CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Next.js 实战入门/课时/渲染模式 SSR / SSG / ISR
RawBlame
课时目录12 节

Next.js 实战入门

  1. 01Next.js 是什么
  2. 02项目创建与目录
  3. 03App Router 与文件路由
  4. 04Server 与 Client 组件
  5. 05数据获取与缓存
  6. 06渲染模式 SSR / SSG / ISR
  7. 07Route Handlers
  8. 08MDX 与课时渲染
  9. 09Tailwind 与样式
  10. 10认证与登录门控
  11. 11读 platform 源码
  12. 12综合练习与部署

渲染模式 SSR / SSG / ISR

static/dynamic、force-dynamic、路线图 SSR 节点。

30 分钟

渲染模式 SSR / SSG / ISR

这一课你将能做到

  1. 用一句话区分静态生成(SSG)、服务端渲染(SSR)、增量静态再生(ISR),并说出各自典型场景
  2. 看 npm run build 输出的路由标记(○ / ● / ƒ),说出每个页面将以什么方式渲染
  3. 掌握 export const revalidate、dynamic 配置与「哪些代码会把页面打成动态」这两件核心工程技能

为什么要学

上一课的缓存决定了「数据打不打后端」,本课的渲染模式决定「页面在什么时候、在哪里被生成」——这是 Next.js 面试出题率最高的一块,没有之一。因为它是选型题:给你一个业务场景,问你用哪种模式,考的是真实工程判断力。

先把三类模式的本质用一张图钉住:

SSG(静态生成):页面在「构建时」生成好,存成静态文件
   构建 → 出 HTML → 所有用户共享同一份 → CDN 直出,最快
SSR(服务端渲染):页面在「每次请求时」生成
   用户请求 → 服务器现场取数渲染 → 返回 HTML → 人人不同
ISR(增量静态再生):SSG + 过期时间
   构建/首次访问 → 缓存 N 秒 → 过期后后台重建 → 兼顾速度与新鲜度

对应到真实业务:产品官网、帮助文档 → SSG;用户个人中心、强实时大盘 → SSR;商品详情、文章页 → ISR。本站课程目录这类内容也适合 ISR 思路,而用户进度页必须走动态。

注意一个易混点:在 Next.js 语境里「动态渲染」≠ Client 渲染。SSR/SSG/ISR 说的是 HTML 在服务端什么时候生成;水合之后的交互永远是 React 的事。上一课的 Server/Client 组件划分,与渲染模式是正交的两个维度。

先建立图像

Next 如何决定一个页面是静态还是动态?看渲染输入里有没有「每次请求才知道的东西」:

页面里出现以下任一情况 → 动态渲染(ƒ):
  1. fetch 使用了 cache: "no-store" 或 revalidate: 0
  2. 读取了 cookies() / headers()
  3. 页面接收并 await 了 searchParams
  4. 读了动态段且未开启 generateStaticParams(见下)
  5. 配置了 export const dynamic = "force-dynamic"

以上都没有 → 静态渲染(○ / ●),构建时出结果

build 输出的标记(务必眼熟):

Route (app)                              Size    Revalidate
● /                                      5 kB           ─   静态
● /docs/[slug]                           …              1h  带参数的 ISR
○ /about                                 1 kB           ─   静态
ƒ /tracks/[slug]/lessons/[lessonSlug]    …              ─   动态(按请求)

三种模式的响应时序对比:

SSG : 用户 ──────→ CDN 边缘节点 ──(命中预生成 HTML)──→ 秒回
SSR : 用户 ──→ 服务器:取数(几十~几百ms) + 渲染 ──→ 返回
ISR : 用户 ──→ 缓存新鲜?──是──→ 秒回
                 └─否──→ 先回旧版,后台重建,下次是新版

跟着做

步骤 1:观察一个默认静态页

// src/app/static-demo/page.tsx
export default function StaticPage() {
  const builtAt = new Date().toISOString();
  return <p className="p-6">本页构建于:{builtAt}</p>;
}

执行 npm run build && npm run start,访问 /static-demo,反复刷新——时间不变。因为它没有动态输入,Next 在 build 时就算好了 HTML,new Date() 只在构建那一刻执行过一次。

步骤 2:让它变成 ISR——加一个导出

// src/app/isr-demo/page.tsx
export const revalidate = 10; // 缓存 10 秒,过期后台重建

export default function IsrPage() {
  const generatedAt = new Date().toISOString();
  return <p className="p-6">本页生成于:{generatedAt}(每 10 秒后台刷新)</p>;
}

build + start 后观察:10 秒内刷新时间不变;超过 10 秒后第一次刷新仍是旧时间(先回旧缓存),再刷新变成新时间(后台已重建)。这正是 stale-while-revalidate,亲手看到一次胜过背十遍。

带动态段的 ISR 需要告诉 Next 有哪些参数可预渲染:

// src/app/tracks/[slug]/page.tsx
export const revalidate = 3600;

export async function generateStaticParams() {
  const tracks = await getTracks(); // 构建时取一次全量列表
  return tracks.map((t) => ({ slug: t.slug }));
}

export default async function TrackPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  return <h1 className="p-6 text-2xl">{slug} 的课程详情(ISR 1h)</h1>;
}

generateStaticParams 返回的参数在 build 时预渲染成静态页;用户访问了列表之外的 slug 时,Next 会按需渲染并缓存它(动态参数的 ISR 兜底)。

步骤 3:做一个真正的 SSR 页

// src/app/ssr-demo/page.tsx
export const dynamic = "force-dynamic"; // 强制每次请求都渲染

export default async function SsrPage() {
  const res = await fetch("http://localhost:8080/api/server-time", {
    cache: "no-store",
  });
  const { time } = await res.json();
  return (
    <main className="p-6">
      <h1 className="text-2xl font-bold">实时服务器时间</h1>
      <p className="mt-2">后端返回:{time}</p>
    </main>
  );
}

build 输出里这页会标 ƒ(Dynamic)。每次刷新时间都变——数据每次现取、HTML 每次现渲。

步骤 4:读 cookies 把页面打成动态(用户级页面的原理)

// src/app/me-demo/page.tsx
import { cookies } from "next/headers";

export default async function MePage() {
  const token = (await cookies()).get("token")?.value;
  return (
    <main className="p-6">
      {token ? <p>已登录,token 以 ****{token.slice(-4)} 显示</p> : <p>未登录</p>}
    </main>
  );
}

不需要任何配置,cookies() 一出现,build 输出自动变 ƒ。这就是「用户个人页为什么天生动态」的机制——读取请求专属信息(cookie/header)意味着结果因人而异,无法预生成共享。

步骤 5:用 build 输出做一次全面体检

npm run build

对着输出的路由表逐行问自己:这页为什么是 ○/●/ƒ?答案总能归结到步骤开头的判定清单。以后每接手一个 Next 项目,先 build 一遍看这张表,渲染架构一目了然——这也是排查「页面为什么总是旧的/为什么这么慢」的第一步。

讲清楚要点

  1. 选型公式:内容对所有人相同且变化少 → SSG;内容因人而异或强实时 → SSR;内容相同但会定期变 → ISR。拿不准就 ISR(先静态后刷新,最平衡)。同一项目可以混用——Next 按「每个路由」独立决定模式。
  2. 静态与动态由「渲染输入」自动推断:没有请求数据的页面天生静态;用了 cookies()/headers()/searchParams/no-store 的页面天生动态。export const dynamic = "force-dynamic" 和 export const revalidate = N 是人工干预的两个总开关(前者强制动态,后者给静态页加过期时间)。
  3. SSR ≠ 慢,SSG ≠ 快就无脑用:SSR 页面可以把数据请求并行化、流式返回(先发骨架),TTFB 依然可观;SSG 用在「该动态而错配成静态」的场景(比如把用户页生成成静态)出的是逻辑事故,比慢严重得多。
  4. ISR 的正确心智:它是「把 SSR 的成本摊薄」——绝大多数请求由缓存承接,后端只在过期重建时被打一次。代价是存在最长 revalidate 秒的数据延迟窗口;不能容忍延迟的数据老老实实走动态。
  5. 渲染模式与本站对照:platform 课时页顶部 export const dynamic = "force-dynamic"——因为要读登录 token、返回个性化锁定状态,个人化页面必须动态;这是真实项目选型的活教材。

你可能会懵的地方

  • ISR 到期那一刻,用户看到的是新是旧? 旧。SWR 语义:过期后首个请求立刻返回缓存旧值,同时后台重建,重建完成后缓存更新。「最多旧 revalidate 秒」就是 ISR 的数据延迟上界,选型时按业务容忍度定这个数。

  • generateStaticParams 和不在名单里的 slug 怎么处理? 名单内的 build 时预渲染;名单外的首次访问时服务端按需渲染并缓存(等价于访问时 ISR),后续用户命中缓存。想改成「名单外一律 404」可配 dynamicParams = false。

  • 静态页不是永久不变,为什么叫「静态」? 「静态」指「生成与请求解耦」,不是「永远不变」。build 全量重跑、revalidate 定时刷新、revalidateTag 主动刷新,都能更新静态产物。静态 = 默认不随请求变化,而非内容死亡。

  • SSR 页面里还能再缓存某个数据吗? 能,两个维度正交。页面动态(每次渲染)不妨碍其中某个 fetch 单独带 revalidate——页面渲染每请求都发生,但那个数据 5 分钟内直接用缓存。反之静态页里的 fetch 也可能因 revalidate 独立刷新。

  • 整站该全选 force-dynamic 图省事吗? 不该。全动态等于放弃 Next.js 最大的性能资产,所有流量都压到渲染层与后端。正确姿势:让默认推断生效,仅对确需个性化的页面强制动态——本站也就是课时/进度这类页面才 force-dynamic。

错法对照

错法问题正解
用户个人中心配了 revalidate = 60A 的页面缓存可能展示给 B用户级页面 force-dynamic / no-store
内容页忘了 generateStaticParams无法构建期预生成,首访变慢列表数据构建时拉一次生成参数
看到页面「数据是旧的」就骂 SSR实际是缓存层级在生效从 Data Cache → Route Cache 逐层排查
全站 export const dynamic = "force-dynamic"放弃静态优化,TTFB 与后端压力全面上升默认推断 + 按页最小干预
在静态页用 new Date() 展示「当前时间」时间是构建时刻的,用户看到冻结值此需求走动态渲染或客户端展示

卡点急救

  1. build 通过,start 后动态页 500:动态页在请求时才取数,多半是生产环境后端不可达。检查生产环境变量里的 API 地址(容器内要用内网地址,本站 API_INTERNAL_URL 就是干这个的)。
  2. ISR 一直不刷新:确认是 npm run start 跑生产版(dev 模式行为不同);多个实例部署时缓存不共享,需一致的失效策略或接自定义缓存 Handler。
  3. cookies() 报错只能在 Server 环境调用:把它放回 Server 组件/Server Action;Client 组件里请用 document.cookie 或由服务端读好经 props 传入。

面试常问

  • SSR、SSG、ISR 的区别与选型?(必考,背熟) SSG 构建时生成、CDN 直出、最快,适合内容稳定的公开页;SSR 每次请求现场渲染、数据最新,适合个性化与强实时页面;ISR 以 revalidate 为周期让静态页后台重建,SWR 语义先回旧值,适合「内容共享但会变化」的场景(商品、文章)。选型三问:内容是否因人而异?变化频率?可容忍多少延迟?同一应用可按路由混用。

  • Next.js 怎么决定页面是静态还是动态? 自动推断 + 显式配置。读取 cookies/headers/searchParams、no-store 的 fetch、未预生成的动态段会把页面判为动态;否则静态。可用 export const dynamic = "force-dynamic" 强制动态、export const revalidate = N 给静态页加过期、generateStaticParams 声明动态段的预渲染名单。build 输出的路由表(●/ƒ 标记)就是最终判定结果。

  • ISR 数据延迟窗口怎么向业务交代? 上界是 revalidate 秒数,且过期后首个请求返回旧值(SWR)。方案:把 revalidate 调到业务可容忍的粒度;对时效敏感的字段拆到客户端单独请求或用 revalidateTag 事件驱动失效;交易类数据一律动态渲染。

自测三问

  1. 一个「课程列表 + 用户学习进度」页面,你会怎么拆分渲染策略?
  2. build 输出里 ○、●、ƒ 各代表什么?哪个标记最该引起警觉?
  3. ISR 第一次过期请求为什么返回旧数据?这个设计的收益是什么?

小结一句话

渲染模式的本质是「页面何时生成」:不变走 SSG、定期变走 ISR、因人而异走 SSR;Next 按「渲染输入是否依赖请求」自动判定,build 路由表是最终真相。

延伸

  • Next.js 官方文档:Rendering
  • 下一课:自己动手写后端——Route Handlers。
加载本节练习…
← 上一课数据获取与缓存下一课 →Route Handlers