在现代 Web 应用中,性能和数据新鲜度是两个关键指标。Next.js 提供了强大的缓存(Caching)与重新验证(Revalidation)机制,帮助开发者在提升性能的同时,确保用户获取到最新数据。本文将深入介绍 Next.js App Router 中的缓存策略及其使用方式。
1. 什么是缓存?
缓存是一种将计算结果(如 API 请求、数据库查询)临时存储起来的技术。当下次请求相同数据时,可以直接从缓存中读取,避免重复计算,从而显著提升响应速度和降低服务器负载。
在 Next.js 中,缓存不仅适用于数据请求,也适用于页面渲染结果。
2. fetch 的缓存与重新验证
默认行为
在 Next.js 的 App Router 中,fetch 请求默认不会被缓存 。但值得注意的是,即使 fetch 不缓存,Next.js 仍会对包含 fetch 的页面进行静态预渲染(Prerendering),并将整个 HTML 页面缓存下来。
强制缓存
你可以通过设置 cache: 'force-cache' 来显式启用缓存:
ts
export default async function Page() {
const data = await fetch('https://api.example.com/data', {
cache: 'force-cache', // 启用缓存
});
return <div>{data.title}</div>;
}
该请求的结果将被缓存,并在后续请求中直接返回。
基于时间的重新验证(Time-based Revalidation)
为了让缓存数据保持"新鲜",Next.js 支持基于时间的重新验证:
ts
export default async function Page() {
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }, // 每小时重新验证一次
});
}
这意味着:缓存最多保留 3600 秒(1 小时),之后下一次请求会触发重新获取数据,并更新缓存。
3. unstable_cache:缓存任意异步函数
除了 fetch,你可能还需要缓存数据库查询、复杂计算等操作。Next.js 提供了 unstable_cache API(注意:前缀 unstable_ 表示该 API 可能在未来变更)。
基本用法
ts
import { unstable_cache } from 'next/cache';
import { getUserById } from '@/lib/data';
export default async function Page({ params }: { params: Promise<{ userId: string }> }) {
const { userId } = await params;
const getCachedUser = unstable_cache(
async () => {
return getUserById(userId);
},
[userId] // 缓存键:确保不同用户的数据独立缓存
);
const user = await getCachedUser();
return <div>{user.name}</div>;
}
配置重新验证与标签
你可以通过第三个参数配置缓存策略:
ts
const getCachedUser = unstable_cache(
async () => getUserById(userId),
[userId],
{
tags: ['user'], // 用于后续按标签清除缓存
revalidate: 3600, // 1 小时后重新验证
}
);
4. 按标签重新验证:revalidateTag
当你更新了用户数据,希望清除相关缓存时,可以使用 revalidateTag。
步骤一:为缓存打标签
无论是 fetch 还是 unstable_cache,都可以添加 tags:
ts
// 使用 fetch 打标签
await fetch('https://api.example.com/user', {
next: { tags: ['user'] }
});
// 或使用 unstable_cache 打标签
const getUser = unstable_cache(
async (id) => db.user.find(id),
['user'],
{ tags: ['user'] }
);
步骤二:在更新操作中触发重新验证
通常在 Route Handler 或 Server Action 中调用:
ts
import { revalidateTag } from 'next/cache';
export async function updateUser(id: string, data: any) {
await db.user.update(id, data);
revalidateTag('user'); // 清除所有带 'user' 标签的缓存
}
✅ 优势:一个标签可关联多个缓存项,实现批量失效。
5. 按路径重新验证:revalidatePath
如果你只想重新验证某个页面(及其数据),可以使用 revalidatePath:
ts
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
await savePost(formData);
revalidatePath('/blog'); // 重新生成 /blog 页面
}
这会触发 Next.js 重新预渲染指定路径,并更新其缓存的 HTML 和数据。
6. 缓存策略对比
| 方法 | 适用场景 | 是否支持 revalidate | 是否支持标签 | 是否适用于非 fetch |
|---|---|---|---|---|
fetch + cache |
API 请求缓存 | ✅(next.revalidate) |
✅(next.tags) |
❌ |
unstable_cache |
数据库查询、自定义函数缓存 | ✅ | ✅ | ✅ |
revalidateTag |
按标签批量清除缓存 | --- | ✅ | --- |
revalidatePath |
重新生成特定页面 | --- | ❌ | --- |
7. 最佳实践
- 优先使用
fetch缓存 :对于外部 API,直接使用fetch的缓存选项最简单高效。 - 敏感数据避免缓存 :如用户个人资料、支付信息等,应设为动态请求(
cache: 'no-store')。 - 合理设置 revalidate 时间:高频更新的数据设短时间(如 60 秒),静态内容可设更长。
- 善用标签管理缓存 :通过
tags实现精准、高效的缓存失效。 - 结合 Server Actions 使用 :在用户操作(如提交表单)后立即触发
revalidateTag或revalidatePath,实现"即时更新"。
结语
Next.js 的缓存系统兼顾了性能 与灵活性 ,通过 fetch、unstable_cache、revalidateTag 和 revalidatePath 等 API,开发者可以构建既快速又实时的应用。掌握这些机制,是构建高性能 Next.js 应用的关键一步。