
一句话:四种渲染方式的本质区别是「HTML 在哪里、什么时候生成」——CSR 在浏览器(首屏慢、SEO 差),SSR 每次请求在服务端,SSG 构建时生成(最快),ISR 静态生成 + 后台增量再生。
一、四种方式的原理
① CSR(Client-Side Rendering)
服务端只返回「空的 HTML + JS」→ 浏览器下载并执行 JS → React 渲染出内容
代表:CRA / Vite SPA
② SSR(Server-Side Rendering)
每次请求时,服务端执行 React 把「完整 HTML」生成好 → 返回给浏览器
→ 浏览器立刻显示 HTML(可见)→ 下载 JS → 水合(绑定事件、变成可交互)
代表:Next.js 的 server rendering / Remix
③ SSG(Static Site Generation)
构建时(build)就把 HTML 生成好,部署到 CDN
→ 请求时直接返回静态文件(最快、最省)
代表:Next.js 的静态导出 / Astro
④ ISR(Incremental Static Regeneration)
静态生成 + 「按需/定时重新生成」——先给用户旧的静态页,
后台在「过期后」重新生成(下次请求得到新版本)
代表:Next.js 的 `revalidate`
一句话记忆:
CSR:HTML 在【浏览器】生成(运行时)
SSR:HTML 在【服务端】生成(每次请求)
SSG:HTML 在【构建时】生成(一次性)
ISR:SSG + 【定期/按需重新生成】
二、对比
| 维度 | CSR | SSR | SSG | ISR |
|---|---|---|---|---|
| HTML 生成时机 | 浏览器运行时 | 每次请求 | 构建时 | 构建时 + 后台再生 |
| 首屏内容可见 | ❌(白屏,等 JS) | ✅(立刻看到 HTML) | ✅(最快) | ✅ |
| 可交互时间(TTI) | 早(已下载 JS) | 较晚(要水合) | 最早 | 最早 |
| SEO | ⚠️ 差(爬虫可能看不到内容) | ✅ 好 | ✅ 好 | ✅ 好 |
| 数据新鲜度 | 实时 | 实时 | 构建时(可能很旧) | 可配置(秒~天) |
| 服务端成本 | 低(只有静态文件) | 高(每请求都渲染) | 极低(CDN) | 低 |
| 首次访问延迟 | 低(静态文件) | 高(等渲染) | 极低 | 极低 |
| 适合 | 后台系统、登录后应用 | 个性化/实时内容 | 博客、文档、营销页 | 电商列表、内容站 |
| TTFB | 低 | 高 | 最低 | 最低 |
⭐ 三个容易混淆的指标:
· FCP/LCP(首次内容/最大内容绘制)——「用户多快看到内容」
· TTI(可交互时间)——「用户多快能操作」
· TTFB(首字节时间)——「服务端多快开始返回」
SSR 的 FCP 很好(HTML 直接可见),但 TTI 未必好(要等 JS 下载 + 水合)。
这一点常被误解为「SSR 一定更快」——准确说法是「SSR 让内容更早可见」。
三、CSR 的两个问题
// 典型 CSR 的 index.html
<!DOCTYPE html>
<html>
<head><title>App</title></head>
<body>
<div id="root"></div> {/* ⭐ 空的!内容靠 JS 生成 */}
<script src="/assets/index-4f3a.js"></script>
</body>
</html>
问题一:首屏「白屏」时间长
下载 JS → 解析 → 执行 → React 渲染 → 请求数据 → 再渲染
→ 用户在看到内容前要等「网络 + 解析 + 执行 + 请求」的总和
问题二:SEO 差
· 传统爬虫(不执行 JS)只会看到空 HTML
· 即使现代搜索引擎能执行 JS,也存在「抓取预算」「渲染超时」的问题
· 社交平台(微信/Twitter/Facebook)的「分享卡片」只读 HTML meta,
如果 meta 是 JS 动态插入的,卡片就抓不到(除非用 SSR/预渲染)
问题三:首屏依赖网络质量(弱网体验差)
⚠️ 什么时候 CSR 是可以接受的?
① 登录后的后台系统(不需要 SEO、用户有耐心等)
② 强交互应用(编辑器、IDE 类)
③ 内部工具
→ 但即使是这些,「首屏白屏 + 骨架」的体验优化仍然值得做(代码分割、预加载)
四、SSR 的完整流程
一次 SSR 请求经历:
服务端:
① 接收请求
② 渲染 React 树 → 生成 HTML 字符串(或流式输出)
③ 同时「脱水」把「渲染时用到的数据」序列化到 HTML 里
(如 <script>window.__DATA__ = {...}</script>)
④ 返回 HTML
浏览器:
⑤ 立刻显示 HTML(用户看到内容 —— FCP 好)
⑥ 下载并执行 JS(React 客户端代码)
⑦ 【水合 Hydration】:
· 复用服务端的 HTML,绑定事件监听
· 校验「客户端渲染结果」与「服务端 HTML」是否一致
· 不一致 → hydration mismatch(警告并可能重新渲染)
⑧ 页面变为「可交互」(TTI)
⚠️ 关键:水合之前,页面是「看得见但点不动」的
—— 这是 SSR 的固有特性(也是「选择性水合」「岛屿架构」要解决的问题)
// Next.js(App Router)里的 SSR
// app/page.tsx —— Server Component 默认是 SSR
export default async function Page() {
const posts = await db.post.findMany(); // ⭐ 服务端直接查数据库
return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>;
}
五、SSG 与 ISR
// SSG:构建时生成(Next.js 里「没有动态数据」的页面默认静态化)
// app/about/page.tsx
export default function About() {
return <div>关于我们</div>; // ⭐ 构建时生成 HTML
}
// ISR:静态生成 + 定期再生(Next.js 的 revalidate)
// app/posts/page.tsx
export const revalidate = 60; // ⭐ 60 秒后「过期」
export default async function Posts() {
const posts = await fetchPosts();
return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>;
}
ISR 的工作方式(Next.js):
① 构建时生成静态页
② 用户访问 → 直接返回静态页(很快)
③ 页面「超过 revalidate 时间」后,第一个访问它的用户:
· 仍然拿到「旧的静态页」(不等渲染)
· 同时后台触发「重新生成」
④ 之后访问的用户拿到新版本
⭐ 这就是 stale-while-revalidate(与 7.5 的思想一致):
先给用户旧内容(快),后台再更新(新)。
// ✅ 按需再生(On-demand Revalidation):内容变更时主动刷新
import { revalidatePath, revalidateTag } from 'next/cache';
export async function createPost(formData: FormData) {
'use server';
await db.post.create({ data: { title: formData.get('title') as string } });
revalidatePath('/posts'); // ⭐ 让 /posts 的缓存立刻失效
// 或 revalidateTag('posts');
}
// 比「等 revalidate 时间」更实时
| 方式 | 数据新鲜度 | 说明 |
|---|---|---|
| SSG(无 revalidate) | 构建时 | 内容变了要重新部署 |
| ISR(时间) | revalidate 秒 |
页面级自动刷新 |
| ISR(按需) | 立刻 | 内容变更时调 revalidatePath/revalidateTag |
| SSR | 每次请求 | 最实时,但成本最高 |
六、流式 SSR 与 RSC
// ✅ 流式 SSR(React 18+):shell 先到,慢的部分后补
// 用 renderToPipeableStream + Suspense 边界
<Suspense fallback={<CommentsSkeleton />}>
<Comments /> {/* ⭐ 服务端先给占位,数据就绪后流式补上 */}
</Suspense>
// ✅ React Server Components(RSC):组件在服务端执行,代码不进客户端包
// app/posts/page.tsx(Server Component,默认)
import { db } from '@/lib/db';
import { LikeButton } from './like-button'; // Client Component
export default async function PostsPage() {
const posts = await db.post.findMany(); // ⭐ 直接访问数据库(这段代码在服务端)
return (
<ul>
{posts.map((p) => (
<li key={p.id}>
{p.title}
<LikeButton postId={p.id} /> {/* 客户端组件负责交互 */}
</li>
))}
</ul>
);
}
流式 SSR + RSC 的收益:
① shell 立刻可见(TTFB 早),慢的部分流式补上
② 服务端组件的代码「不进客户端包」→ 包体积显著减小
③ 可以在服务端「直接访问数据库」→ 少一层 HTTP 往返
④ 配合 Suspense 可以做「渐进式渲染 + 选择性水合」
⚠️ 代价与限制:
① 心智模型变复杂(Server/Client 边界,见 9.3)
② 依赖框架(Next.js / Remix 等)
③ 调试与部署更复杂(服务端运行环境、缓存策略)
七、选型决策
① 需要 SEO 吗?
├─ 不需要(后台系统、内部工具)→ CSR 就够了(可以用 Vite)
└─ 需要 → 继续
② 内容「所有人都一样」且「变化不频繁」吗?(博客、文档、营销页)
├─ 是 → SSG(配 ISR 处理内容更新)——最快、最省
└─ 否 → 继续
③ 内容「因人而异」或「必须实时」吗?(用户中心、实时数据)
├─ 是 → SSR(或「SSR + 客户端数据获取」混合)
└─ 否 → ISR(静态 + 定期更新,性价比最高)
④ 需要「首屏极快 + 强交互」吗?(电商、内容平台)
→ SSR/RSC + 流式渲染 + 客户端数据获取(混合策略)
⭐ 实践中最好是「混合」:
首页/列表 → ISR(快 + 可更新)
详情页 → SSR 或 ISR
登录后页面 → CSR(或 SSR 拿首屏 + 客户端交互)
静态页(关于/条款)→ SSG
八、常见坑
// ① 认为「SSR 一定比 CSR 快」
// → SSR 的 FCP 好,但 TTI 可能更差(要等 JS + 水合)
// ② 认为「用了 SSR 就不需要客户端数据获取」
// → 首屏数据用 SSR 更好,但「交互产生的数据」(筛选/分页)仍需客户端请求
// ③ 在 SSR 组件里用「浏览器 API」
window.localStorage.getItem('x'); // ❌ 服务端没有 window
// ✅ 用 cookie(通过 request headers)或在客户端 useEffect 里读
// ④ 在 SSR 组件里用「随机值 / 当前时间」渲染内容
const id = Math.random(); // ⚠️ hydration mismatch
// ⑤ 认为「SSG 内容永不过期」(没有 revalidate 时,内容变了要重新部署)
// ⑥ 用 SSR 渲染「完全静态的页面」(浪费服务端资源,应该用 SSG)
// ⑦ 忘记「SSR 的数据要脱水给客户端」(否则客户端再请求一次,甚至 mismatch)
// ⑧ 用「ISR + 个性化内容」(ISR 是共享缓存,会串用户数据!)
// ⑨ 在 SSR 中「把敏感数据渲染进 HTML」(任何人都能看到源码)
const token = req.cookies.token; // ⚠️ 别渲染到 HTML 里
// ⑩ 忽略「服务端渲染的错误处理」(一个组件抛错可能让整个请求 500)
// ⑪ 认为「RSC 取代了 SSR」(RSC 是「组件在哪执行」,SSR 是「HTML 何时生成」)
// ⑫ 在「边缘运行时」用了 Node 专属 API(如 fs)
// ⑬ 用 SSR 但不做「缓存」(每个请求都查数据库,成本高、更慢)
// ⑭ 忘记水合成本(大量静态 HTML + 大量 JS → 水合很慢)
// → 用「岛屿架构」(Astro)或「RSC + 少的客户端组件」缓解
// ⑧ 的说明:ISR 是「共享缓存」,不能用来渲染「因人而异」的内容
// ❌ 页面里渲染了 userId 相关内容,但页面是 ISR 静态化的
// ✅ 个性化部分用「客户端获取」或用「动态渲染(SSR)」
九、面试延伸
- 「CSR / SSR / SSG / ISR 有什么区别?」
本质区别是「HTML 在哪里、什么时候生成」:① CSR——服务端只返回空 HTML + JS,内容由浏览器执行 JS 渲染(首屏白屏、SEO 差,但服务端成本低);② SSR——每次请求时服务端生成完整 HTML(首屏内容可见、SEO 好、数据实时,但服务端成本高、TTFB 慢);③ SSG——构建时生成 HTML 并部署到 CDN(最快最省,但内容可能很旧);④ ISR——静态生成 + 后台定期/按需再生(既有静态的「快」,又能更新内容)。 -
「SSR 一定比 CSR 快吗?」
不一定。准确说法是「SSR 让内容更早可见(FCP/LCP 更好)」,但它的 TTI(可交互时间)可能更差——因为浏览器仍需「下载 JS → 水合(绑定事件)」,而水合期间页面是「看得见但点不动」的。如果 JS 很大、水合很慢,用户体验反而可能是「刚看到内容就想点,但点了没反应」。所以优化的目标应该是「用更少的 JS 完成水合」(代码分割、减少客户端组件、岛屿架构)。 -
「SSR 的完整流程是什么?」
服务端:① 接收请求 → ② 渲染 React 树为 HTML → ③ 把渲染用到的数据「脱水」序列化进 HTML(如window.__DATA__)→ ④ 返回 HTML。浏览器:⑤ 立刻显示 HTML(内容可见)→ ⑥ 下载并执行客户端 JS → ⑦ 水合(复用 HTML、绑定事件、校验一致性)→ ⑧ 变为可交互。关键点是第 ⑦ 步的「水合」——它是 SSR 的核心机制,也是「hydration mismatch」问题(见 9.4)的来源。 -
「什么是 ISR?」
ISR(Incremental Static Regeneration)是「静态生成 + 后台再生」:构建时生成静态页;页面超过revalidate时间后,第一个访问它的用户仍然拿到旧的静态页(不等渲染),同时后台触发重新生成,之后的用户拿到新版本。这本质上是「页面级的 stale-while-revalidate」。此外还支持「按需再生」——内容变更时调用revalidatePath/revalidateTag立刻让缓存失效(比等时间更实时)。 -
「什么时候用 CSR?」
三种场景:① 登录后的后台系统 / 内部工具——不需要 SEO,用户愿意等(且首屏可以通过代码分割优化);② 强交互应用——编辑器、IDE 类工具(客户端的计算与状态占主导,服务端渲染收益小);③ 数据高度个性化且实时——反正要客户端请求,SSR 的意义有限(但首屏骨架仍可优化)。判断标准是「SEO 与首屏可见性是否关键」——如果不关键,CSR 的开发与部署成本最低。 -
「SSG 和 ISR 怎么选?」
看「内容更新频率与实时性要求」:① 几乎不变(关于页、条款、文档)→ SSG(无 revalidate);② 定期更新(博客列表、商品列表)→ ISR + 时间(如revalidate = 60);③ 内容变更时要立刻生效(CMS 发布)→ ISR + 按需再生(revalidatePath);④ 必须实时且因人而异(用户中心)→ SSR 或「SSG 外壳 + 客户端数据」。关键坑:ISR 是共享缓存,不能渲染个性化内容(否则会把 A 用户的数据给 B 用户看)。 -
「什么是流式 SSR?它解决什么问题?」
流式 SSR 指「服务端边渲染边发送 HTML」:先把「不挂起的部分(shell)」渲染完立即发送(TTFB 更早、首屏更快可见),遇到<Suspense>边界里「慢的部分」时先发送fallback 的 HTML + 一段脚本,数据就绪后再把真正内容流式发送并由客户端无缝替换。它解决的是「一个慢请求阻塞整个页面」的问题(传统 SSR 必须等所有数据就绪才能返回 HTML)。实现上要用renderToPipeableStream(React 18+)。 -
「RSC(Server Components)和 SSR 有什么区别?」
它们回答的是不同的问题:① SSR 回答「HTML 什么时候生成」(每次请求时、在服务端)——它仍然会「渲染完整组件树、把 JS 发给客户端、在客户端水合」;② RSC 回答「组件在哪里执行」——服务端组件在服务端执行,其代码不进客户端包(可以直接访问数据库、用大依赖而不增加客户端体积),只有被标记"use client"的组件才会进客户端包并参与水合。两者可以同时使用(如 Next.js App Router:用 RSC 减少客户端 JS,同时用 SSR/ISR 决定 HTML 生成方式)。
一句话速记
四种方式的本质区别是「HTML 在哪里、什么时候生成」——CSR(浏览器运行时,首屏白屏 + SEO 差但成本最低)、SSR(每次请求,内容实时 + SEO 好但 TTFB 慢、TTI 未必好)、SSG(构建时,最快最省但可能很旧)、ISR(静态 + 后台/按需再生,本质是「页面级 stale-while-revalidate」);SSR 的流程是「服务端渲染 HTML + 数据脱水 → 浏览器显示 → 水合 → 可交互」(水合期间看得见但点不动);选型口诀是「不要 SEO 用 CSR;不变的内容用 SSG;定期更新用 ISR;实时/个性化用 SSR」,实践上多为混合;ISR 不能渲染个性化内容(共享缓存会串用户);RSC 解决的是「组件在哪执行」(不进客户端包),与 SSR 是两件事,可以同时用。



最新评论
读过书不知道欧·亨利的人少。教科书上选文有
这小生活不错呀
不错,必须顶一下!
看着你还在坚持,很好
看来忙了也没时间更新博客了
NIce。学习了。。。。
网站不错!!!!
简洁实用,好文章!