数据缓存与失效
数据缓存与失效 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
缓存是「少干活」,失效是「该新就新」。读完你应该能说清 Next.js 16 的默认缓存规则,用 revalidatePath / revalidateTag 在数据变化时精准失效,让笔记和进度界面不再展示旧记录。
问题场景
课程页顶部显示「已学完 3 节」。你在笔记页写完一节,回到课程页——数字还是 3。改了内容、更新了进度,界面却躺在旧记录上,这就是缓存失效没做对。
核心解释
先对齐 Next.js 16 的默认规则,两层数据两种命运:
服务端数据层:fetch 默认不缓存。 现代 Next.js 里 fetch 请求默认每次执行都拿最新数据(早期的默认缓存已被移除)。如果你的数据来自数据库直查(如 N07 的 node:sqlite),它本来就不经过 fetch 缓存。真正需要留意的是页面级缓存:没有任何动态信号的页面会在 build 时被预渲染成静态 HTML,之后每次请求都发同一份——数据变了页面不换。给路由段声明重新验证节奏可以开启 ISR:
// app/courses/page.tsx
export const revalidate = 60; // 每 60 秒允许重新生成一次
export default async function CoursesPage() {
const courses = await listCourses();
return <CourseList courses={courses} />;
}
客户端 Router Cache:导航时可能复用旧渲染。 为了导航流畅,Next.js 会在浏览器里缓存访问过的路由渲染结果,预取的静态段默认有 5 分钟复用窗口。这就是「改完数据、点回上一页看到旧内容」的第二个来源。
失效的手段是在写操作里主动声明「哪些缓存作废」——把失效动作和写动作绑在一起,这是 Server Actions 的标准姿势:
// app/actions.ts
"use server";
import { revalidateTag } from "next/cache";
export async function completeLesson(lessonId: string) {
await markLessonDone(lessonId); // 写库
revalidateTag("progress"); // 失效所有打了这个标签的缓存
}
// 数据读取处打标签
const progress = await getProgress(userId, { next: { tags: ["progress"] } });
revalidatePath("/courses") 则按路径失效,适合不想维护标签体系的小项目。两者的区别:tag 是「按数据种类失效」(进度变了,所有展示进度的页面一起失效),path 是「按页面失效」。写操作所在的当前页面,Action 完成后会自动带回新的渲染结果,界面立即更新——需要你手动失效的是其他引用了这份数据的页面和 Router Cache 里的旧渲染。
示例拆解
把「改进度 → 看到新数字」的链路串起来:
- 课程详情页读进度时打上
tags: ["progress"],页面声明revalidate = 60; - 「标记完成」按钮触发 Server Action:写库 →
revalidateTag("progress"); - 验证一:点击后当前页的进度数字立刻 +1(Action 响应自动带回新渲染);
- 验证二:导航到列表页(预取过的旧渲染),再进入详情页,数字正确——tag 失效把 Router Cache 里的旧渲染一并作废了;
- 验证三:把 revalidateTag 那行注释掉再改进度,重走一遍,观察旧数字复活——这就是失效缺失的现场。
动手任务
在项目里完成三个小改动:
- 给课程页声明
revalidate = 60,在构建输出里确认它变成 ISR 路由(build 输出里显示 Revalidate 标记)。 - 实现「标记完成」Server Action:写库 + revalidateTag("progress"),读端打上同名标签。
- 故意注释掉 revalidateTag 复现旧数据问题,再用 revalidatePath 替换标签方案修复,对比两者的失效范围。
常见错误
- 只在读端缓存、忘了在写端失效:缓存窗口期内用户看到旧数据;失效动作必须和写操作绑在同一个事务性质的函数里。
- revalidatePath 用了不匹配的路径:动态路由要用
/courses/[slug]或具体路径匹配,路径写错等于没失效。 - 以为 revalidateTag 会立即重新拉数据:它是「作废」,下次访问才重建;不要指望调用它后同一请求周期里立刻拿到新值。
- 在客户端组件里直接调 revalidatePath:它是服务端 API,只能出现在 Server Action 或服务端代码里;客户端只负责触发 Action。
检查与复盘
完成下方检查题确认理解。自查清单:
- 说得清两层数据的去向:服务端 fetch 默认不缓存、Router Cache 在客户端复用
- 每个写操作都绑定了对应的失效调用,tag 与 path 的选择有理由
- 验证过「改数据 → 当前页新 → 其他页也新」的完整链路
参考资料
- Next.js 文档:revalidatePath —— 按路径失效的参数规则
- Next.js 文档:revalidateTag —— 按标签失效的用法
- Next.js 文档:缓存指南 —— 各层缓存机制与默认行为的总览
读完这一课了?
标记完成后,工作台会安排接下来的学习。