ISR 与静态再生
"use cache" + cacheLife({ revalidate: 60 }) 的增量静态再生:产物预渲染并缓存,过期后后台重建;generateMetadata 与页面共享同一份缓存条目
这一页是什么时候生成的
generatedAt 在缓存产物生成时取值并随之缓存 —— 它不变,正说明命中了缓存
生成时刻(UTC)
2026-11-09T05:20:58.953Z
60s 内刷新,此刻不变;60s 后第一次刷新拿到的仍是旧值(同时触发后台重建),第二次刷新才会看到新时刻 —— 与旧 revalidate=60 的 stale-while-revalidate 语义逐点一致。
ISR 的旧模型与新模型
语义没变,变的是声明位置:从页面壳上的配置,下沉到取数函数上的指令
旧模型(Next 15 及以前,本站已不再使用)
// app/rendering/isr/page.tsx —— 旧模型(Next 15 及以前)
// route segment config:整页一把开关,页内任何取数都被卷进同一个窗口
export const revalidate = 60; // 60s 后下一个请求触发后台重建
export default async function Page() {
const posts = await getPosts(); // 取数代码本身没有任何缓存标记
return <PostList posts={posts} />;
}本站现行(cacheComponents)
// topics/rendering/isr/cached-posts.ts —— 本站正在运行的真实代码
import { cacheLife, cacheTag } from "next/cache";
export async function getIsrPosts() {
"use cache"; // 显式声明:这份产物可缓存
cacheLife({ revalidate: 60 }); // 60s 窗口,语义对齐旧 revalidate = 60
cacheTag("posts"); // revalidateTag("posts") 可按需失效
const { posts, source } = await getPostsWithStatus();
return { posts, source, generatedAt: new Date().toISOString() };
}
// 缓存声明从「页面壳上的一行配置」下沉到「取数函数上的一条指令」:
// 粒度从整页细化到函数,同一页里不同数据可以各有各的窗口- 对应关系:
export const revalidate = 60→cacheLife({ revalidate: 60 });按需失效从「fetch 带 tag」变为cacheTag+revalidateTag - generateMetadata 与页面调的是同一个
getIsrPosts():React cache() 管单次渲染内去重,"use cache" 管跨请求共享 —— 两层都在真实生效 - 一个容易踩的坑:数据源
data/blog.ts的 getPosts 同时被 /api/blog 使用,缓存声明必须留在调用方(本站的做法), 写进数据源会把 API 路由也拖进缓存
ISR 文章列表(来自 jsonplaceholder)
构建期预渲染 + 60s 后台重建;卡片点击进入动态路由专题的详情演示
sunt aut facere repellat provident occaecati excepturi optio reprehenderit
quia et suscipit suscipit recusandae consequuntur expedita et cum reprehenderit molestiae ut ut quas totam nostrum rerum est autem sunt rem eveniet architecto
qui est esse
est rerum tempore vitae sequi sint nihil reprehenderit dolor beatae ea dolores neque fugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis qui aperiam non debitis possimus qui neque nisi nulla
ea molestias quasi exercitationem repellat qui ipsa sit aut
et iusto sed quo iure voluptatem occaecati omnis eligendi aut ad voluptatem doloribus vel accusantium quis pariatur molestiae porro eius odio et labore et velit aut
eum et est occaecati
ullam et saepe reiciendis voluptatem adipisci sit amet autem assumenda provident rerum culpa quis hic commodi nesciunt rem tenetur doloremque ipsam iure quis sunt voluptatem rerum illo velit
nesciunt quas odio
repudiandae veniam quaerat sunt sed alias aut fugiat sit autem sed est voluptatem omnis possimus esse voluptatibus quis est aut tenetur dolor neque
dolorem eum magni eos aperiam quia
ut aspernatur corporis harum nihil quis provident sequi mollitia nobis aliquid molestiae perspiciatis et ea nemo ab reprehenderit accusantium quas voluptate dolores velit et doloremque molestiae
magnam facilis autem
dolore placeat quibusdam ea quo vitae magni quis enim qui quis quo nemo aut saepe quidem repellat excepturi ut quia sunt ut sequi eos ea sed quas
dolorem dolore est ipsam
dignissimos aperiam dolorem qui eum facilis quibusdam animi sint suscipit qui sint possimus cum quaerat magni maiores excepturi ipsam ut commodi dolor voluptatum modi aut vitae
nesciunt iure omnis dolorem tempora et accusantium
consectetur animi nesciunt iure dolore enim quia ad veniam autem ut quam aut nobis et est aut quod aut provident voluptas autem voluptas
optio molestias id quia eum
quo et expedita modi cum officia vel magni doloribus qui repudiandae vero nisi sit quos veniam quod sed accusamus veritatis error
et ea vero quia laudantium autem
delectus reiciendis molestiae occaecati non minima eveniet qui voluptatibus accusamus in eum beatae sit vel qui neque voluptates ut commodi qui incidunt ut animi commodi
in quibusdam tempore odit est dolorem
itaque id aut magnam praesentium quia et ea odit et ea voluptas et sapiente quia nihil amet occaecati quia id voluptatem incidunt ea est distinctio odio
SSG / ISR / SSR 的语义对照
三种策略的语义不随模型变;变的只是声明方式(旧配置 → 新指令)
| 策略 | 预渲染时机 | 数据新鲜度 | 本站实现 | 适用场景 |
|---|---|---|---|---|
| SSG | 构建期一次 | 冻结在构建时刻 | 无取数 / 取数标 "use cache" 不设时限 | 内容稳定、人人一致 |
| ISR | 构建期 + 过期后台重建 | 最长落后一个 revalidate 窗口 | 本页:cacheLife({ revalidate: 60 }) | 会更新但容忍分钟级延迟 |
| SSR | 不预渲染,每请求现算 | 实时 | 默认动态;或 connection() 显式声明 | 强实时 / 千人千面 |
什么时候别用 ISR
决策要点,而非 API 背诵
- 数据必须实时(库存、仪表盘)→ 保持默认动态(或客户端取数),ISR 的 stale 窗口不可接受
- 内容千人千面(依赖 cookie/会话)→ ISR 缓存的是「所有人共享的一份」,个性化内容会被错误复用
- 更新频率远高于 revalidate 周期 → 缓存永远过期,退化为每请求重建,不如直接用动态渲染或按需 revalidateTag