Next.js 的 SSR、SSG、ISR 和 App Router 怎样理解?
在 Next.js App Router 里,SSR、SSG、ISR 不再靠 getServerSideProps 这类函数区分,而是由组件用到的数据决定:没用到 cookies、headers、searchParams 等请求时 API 的部分默认预渲染(相当于 SSG);用到了就在请求时渲染(相当于 SSR);设置 revalidate 或按需失效后在后台重新生成(ISR)。Next.js 16 新项目默认开启 Cache Components,静态和动态的边界细到组件级。
简答
一个博客站,文章页每个人看到的都一样,评论区和登录状态因人而异。在 Pages Router 时代,你要给整个页面选
getStaticProps(SSG)或getServerSideProps(SSR);到了 App Router,页面默认是服务端组件,Next.js 看你在组件里用了什么,自己决定哪部分在构建时生成,哪部分在请求时生成。规则是这样的:没有用到
cookies()、headers()、searchParams、draftMode()这些请求时 API 的部分,默认预渲染,结果可以放到 CDN 上,这就是 SSG。用到了,就变成请求时渲染,也就是 SSR。想让预渲染的页面过一段时间更新,就在路由里写export const revalidate = 60,或者数据变化后调用revalidatePath、revalidateTag,这就是 ISR。这样做的好处是不用为整页做二选一。Next.js 官方把它描述为“静态和动态的边界在组件级,而不是路由级”:一个页面可以有立即返回的静态外壳,动态部分放在
Suspense里流式补上。要注意版本:以上按 Next.js 16 的 App Router 文档。16 版新建项目默认开启 Cache Components,缓存要用
'use cache'显式声明;没开启时沿用旧模型。另外 ISR、Server Actions 等都需要 Node.js 服务器,用output: 'export'导出成纯静态站时不支持。
先分清 App Router 和 Pages Router
Next.js 有两套路由。官方文档的定义是:App Router 是较新的路由,支持服务端组件等新 React 特性;Pages Router 是最初的路由,仍然受支持。App Router 放在 app/ 目录,Pages Router 放在 pages/ 目录。
getStaticProps、getServerSideProps、getStaticPaths 只属于 Pages Router。面试时如果说“Next.js 用 getServerSideProps 做 SSR”,最好补一句“这是 Pages Router 的写法”。三种渲染方式本身的取舍见《CSR、SSR、SSG 和混合渲染如何选择?》,这里只讲 Next.js 里怎么落地。
App Router 里什么时候是静态,什么时候是动态
官方术语表把“预渲染”定义为在构建时或在重新验证时于后台渲染组件,结果是 HTML 和 RSC Payload,可以缓存并从 CDN 提供;没有使用请求时 API 的组件默认就会预渲染。反过来,组件一旦用了请求时 API,就会转为“动态渲染”,在请求时渲染。
请求时 API 包括 cookies()、headers()、searchParams 和 draftMode()。所以判断一个页面是 SSG 还是 SSR,不看你写了哪个函数名,而是看它读没读请求相关的数据。
没开 Cache Components 时,还可以用路由段配置强制指定:export const dynamic = 'force-dynamic' 让路由在每次请求时为每个用户渲染;'force-static' 强制预渲染,并让 cookies()、headers()、useSearchParams() 返回空值。
ISR 在 App Router 里怎样写
下面是官方 ISR 指南里的结构(未开启 Cache Components 的写法):
// app/blog/[id]/page.tsx
export const revalidate = 60;
export async function generateStaticParams() {
const posts = await fetch('https://api.vercel.app/blog').then((res) => res.json());
return posts.map((post) => ({ id: String(post.id) }));
}
export default async function Page({ params }) {
const { id } = await params;
const post = await fetch(`https://api.vercel.app/blog/${id}`).then((res) => res.json());
return <h1>{post.title}</h1>;
}
官方对这个例子的说明可以归纳成几步。next build 时,按 generateStaticParams 返回的列表给每篇文章预渲染一个页面。60 秒内的请求都直接拿缓存。60 秒过后,下一个请求仍然先拿到旧页面,同时后台开始重新生成。生成成功后,再下一个请求才拿到新页面。如果请求了构建时没生成的 /blog/26 且文章存在,会按需生成;文章不存在则返回 404。
所以 ISR 的“过期”不是定时器到点就重建,而是“过期后的第一个请求触发后台重建”。官方建议把时间设长一些,比如一小时,需要更精确就用按需失效。还要注意,revalidatePath 只是让缓存失效,重新生成发生在下一次请求时。
Cache Components 改变了什么
Next.js 16 文档写明,用推荐的 create-next-app 默认配置新建的 App Router 项目,已经开启了 Cache Components;已有项目需要在 next.config.ts 里设置 cacheComponents: true。
开启后,缓存靠 'use cache' 指令显式声明,可以放在异步函数或组件顶部。需要每次请求都拿新数据的组件不要加 'use cache',而是包在 <Suspense> 里:fallback 随预渲染的静态外壳一起发出,异步部分在请求时运行并流式补上。这就是官方说的“部分预渲染”。缓存时长、标签和失效的细节见《Next.js App Router 中的几层缓存怎样区分?》。
这种组件级边界也有代价。官方“渲染理念”一文指出,它把复杂度从应用代码转移到了托管平台:需要支持流式响应;多实例部署时要协调缓存失效;HTML 和 RSC Payload 要保持一致。
能不能部署到纯静态托管
可以,但只能用一部分功能。在 next.config 里设置 output: 'export',构建结果是一组静态文件。官方列出的不支持项包括:没有 generateStaticParams 或开着 dynamicParams: true 的动态路由、依赖请求的 Route Handler、cookies、rewrites、redirects、headers、Proxy、ISR、默认 loader 的图片优化、Draft Mode 和 Server Actions。
常见追问
App Router 里所有组件都在服务端渲染吗?
页面和布局默认是服务端组件。需要交互或浏览器 API 时,用
'use client'声明客户端组件。客户端组件在首次加载时同样会被服务端渲染成 HTML,再在浏览器 hydration,区别是它的代码会发到浏览器。两者关系见《React Server Components 和 SSR 是同一件事吗?》。
ISR 和 SSR 加 CDN 缓存有什么区别?
效果相近,都是先返回缓存、过期后更新。区别是 ISR 由 Next.js 管理:它知道哪些页面要预生成,能按路径或标签精确失效,并自动加上合适的
Cache-Control响应头。自己在 CDN 上缓存 SSR 结果,失效规则就得自己维护。
generateStaticParams 没列出的路径会怎样?
默认
dynamicParams为 true,首次请求时按需生成并缓存。设成 false 时,未列出的路径直接返回 404。