🌍 一、前言:当你的分页在"想"下一页
是否有过这种痛苦?
你打开一个博客或商品列表:
第一页刷得飞快,第二页却像经历了人生的 HTTP 旅程。
👇
erlang
Loading...
Still loading...
You start doubting your WiFi...
Then realize,是你的后端忘了缓存。
于是我们发现:
在 Next.js 世界里,分页 + 缓存 = 性能巅峰的组合拳 🥊。
🧭 二、底层机理:数据获取的 "三重奏"
我们先从数据流的角度,看看 Next.js 的"缓存哲学":
vbscript
Browser Request
↓
Next.js Server (fetch / db query)
↓
Data Cache Layer (ISR / Fetch Cache / Edge Cache)
↓
Database / API
这意味着每次你拉取分页数据,Next.js 可能会:
- 直接从缓存读(如果有)
- 用缓存策略判断要不要打后端
- 按设定的时间再更新缓存
🎩 小结:
"Next.js 的缓存,有点像一个学会偷懒的黑客。"
它不会每次都算,而是靠"策略智能"决定什么时候该睡、什么时候该醒。
🔥 三、Next.js 缓存基础:fetch 的三种缓存策略
在 Next.js 13+ 中,fetch 不只是发请求,它是缓存的入口。
🧱 (1) 默认:force-cache
内容静态缓存,永不主动更新 ,除非重新部署。
适合静态资源、首页、热门博客等。
csharp
// 静态缓存模式
const data = await fetch("https://api.example.com/posts", {
cache: "force-cache", // 默认
});
⚙️ (2) 动态:no-store
跳过缓存,每次新拉取,适合实时数据,如最新评论。
csharp
// 每次请求都去服务器拿
const comments = await fetch("https://api.example.com/comments", {
cache: "no-store",
});
⏰ (3) ISR(增量静态再生成):revalidate
在缓存与实时之间取得平衡。
php
// 每隔 60 秒刷新一次
const data = await fetch("https://api.example.com/posts", {
next: { revalidate: 60 },
});
📜 比喻一下:
force-cache: "佛系缓存"------我好久没更新了。no-store: "焦虑缓存"------我不相信过去。revalidate: "哲学缓存"------真理总得定期修正。
📦 四、分页的性能灾区,以及我们如何救赎它
分页看似简单,但一旦数据量大,处理得不好就是灾难。
问题通常有👇几种:
- 每页都独立查询(数据库疲惫)。
- 上一页下一页缓存冲突。
- 客户端请求重复触发。
😎 解决方式一:请求层缓存 + 参数命中
在服务端,分页参数其实只是请求 key 的一部分,可以缓存起来。
javascript
async function getPosts(page = 1) {
const cacheKey = `posts-page-${page}`;
const res = await fetch(`https://api.example.com/posts?page=${page}`, {
next: { revalidate: 120 }, // 每两分钟刷新缓存
});
return res.json();
}
Web 平台将每一页都"缓存隔离",就像书签插在不同章节里。
🧠 解决方式二:客户端分页预取(Prefetch)
Next.js 的 Link 组件天生支持预取:
当鼠标悬停在"下一页"按钮上,就自动把下一页的数据请求提前拉走。
javascript
import Link from "next/link";
<Link href="/posts?page=2" prefetch>
下一页 →
</Link>
用户点击即刻加载,连 Loading 动画都未来得及闪现。
就像你以为 AI 是预测对话,其实它早已准备好下一句。
🧩 解决方式三:边缘缓存(Edge Caching)
对于 Web 分发型项目,可以直接利用 Vercel Edge Network 的缓存。
arduino
export const revalidate = 60; // 每页边缘缓存 60 秒生效
优点:
- 用户命中地理位置最近的缓存副本;
- 数据不必跨半个地球;
- 让"分页"在边缘飞起来 🪶。
⏱ 五、缓存与分页的融合示意图
ini
用户请求 ?page=2
↓
Next.js 服务端检查缓存
↓
✅ 有缓存 → 直接返回内容
❌ 无缓存 → fetch 数据 + 写入缓存
↓
返回内容给客户端
↓
下一页预取开始 👀✨
⏳
Pagination 变成了动态拿数据 + 缓存守护的双螺旋结构。
🧮 六、底层原理:缓存的"生命周期"
Next.js 的缓存并非传统 Redis,而是一种层叠存储思想:
arduino
App Routes Cache
↳ Edge Cache
↳ Memory Cache
↳ File Cache
↳ Remote API
请求的每次命中,会先走层级匹配,越靠上越快 。
这就像计算机的多级缓存(L1-L2-L3),
CPU 靠时间换空间,Web 靠策略换速度。
🕹 七、实践:缓存驱动分页组件
来一段实用的範例,用缓存 + 懒加载实现「丝滑」分页体验 ✨:
javascript
// app/posts/page.jsx
import React, { Suspense } from "react";
import PostsList from "./PostsList";
import Loading from "./Loading";
export const revalidate = 120; // ISR 缓存两分钟
export default async function Page({ searchParams }) {
const page = parseInt(searchParams.page || "1");
return (
<Suspense fallback={<Loading />}>
<PostsList page={page} />
</Suspense>
);
}
javascript
// app/posts/PostsList.jsx
async function getPosts(page) {
const res = await fetch(`https://api.example.com/posts?page=${page}`, {
next: { revalidate: 120 },
});
return res.json();
}
export default async function PostsList({ page }) {
const posts = await getPosts(page);
return (
<div>
<h2>📰 第 {page} 页内容</h2>
{posts.map((p) => (
<article key={p.id}>{p.title}</article>
))}
</div>
);
}
🥳 在用户点击 "下一页" 时:
- 前一页缓存命中 ✅
- 下一页预取中 🚀
- 切换零延迟 🌈
📊 八、性能收益实测(逻辑示意)
| 测试项 | 缓存优化前 | 缓存优化后 |
|---|---|---|
| TTFB (首字节响应) | 1500 ms | 200 ms |
| 网络往返次数 | 3 次 | 1 次(缓存命中) |
| 用户点击延迟 | 可感知 | 无感知 |
| API 调用负载 | 高 | 降低 70% 以上 |
"性能指标下降时,工程师的心率才会上升。"
🧠 九、哲学总结:缓存,是时间的艺术
缓存的本质,是一次对时间维度的优化。
分页的本质,是一次对空间维度的分割。
而 Next.js 的设计美学,就是让时间与空间在服务端汇合。
"当你能让服务器记住昨天的答案,
用户才能在今天体验明天的速度。" 🚀
📘 十、小结 ASCII 图
scss
🧠 用户请求页 2
↓
⚙️ Next.js 检查缓存
↓
⏱ 有缓存 → 命中返回
❌ 否则 → fetch + 写入缓存
↓
🚀 前端预取下一页
↓
🔁 Repeat (丝滑体验)
📚 延伸阅读
- Next.js 官方文档:Data Fetching and Caching
- Vercel Performance Whitepaper
- Edge Functions with ISR Explained
🎨 结语
"缓存,是给服务器的记忆;分页,是给用户的节奏。
当两者协奏,网页不再只是被加载,而是在流动中被'演奏'的。" 🎶
愿你的 Next.js 页面,快到浏览器都恋爱。 💖