这些年,我在好几个项目里栽过同一种跟头:页面是能跑,但不是首屏白屏两秒,就是 SEO 怎么都上不去,要么就是服务器被一个本可以静态化的页面活活拖垮。每一次,根上的原因都是同一个——我没想清楚,这个页面到底该用【哪一种渲染策略】。CSR、SSR、SSG、ISR 这四个词,我早就都"听过",也都"会用"——会用,指的是我能照着框架文档把对应的那几个函数写对。但"会用一个 API"和"知道什么时候该用它",中间隔着一整条我交了不少学费才填平的沟。我曾经给一个一年都更新不了几次的公司官网用了 SSR,每个访客来都让服务器现渲染一整遍,纯属浪费;也曾经给一个数据每秒都在变的实时看板用了 SSG,结果用户看到的永远是构建那一刻的"老黄历";还把一个有几十万商品、且商品时常上下架的电商,用纯 SSG 去烤,一次全量构建跑了四十分钟,改一个字都得重烤一遍。这些坑逼着我把 CSR / SSR / SSG / ISR 这四种渲染策略——它们各自到底在【什么时候、把页面放在哪里、用什么时刻的数据】生成出来,各自的代价和适用场景,以及面对一个具体页面该怎么做工程选型——从头到尾彻底理清了一遍。本文是这份梳理的完整复盘。
问题背景:同一个页面,四种"长出来"的方式
一个"页面"要被用户看到,本质上要回答三个问题:
- ★ 在【哪里】渲染? 浏览器 / 服务器 / 构建机
- ★ 在【什么时候】渲染? 构建时 / 请求时 / 用户打开后
- ★ 用的是【什么时刻】的数据? 构建那一刻 / 请求那一刻
★★ CSR / SSR / SSG / ISR,就是这三个问题的四种不同组合。
它们之间【没有高下之分】,只有"适不适配"之分。
四种策略一句话速记:
- CSR(客户端渲染):服务器只给空壳,浏览器下载 JS 后自己渲染
- SSR(服务端渲染):每次请求,服务器现场渲染好完整 HTML
- SSG(静态生成) :上线前构建时,就把页面全部烤成静态 HTML
- ISR(增量静态再生):SSG + 一个"过期后自动在后台翻新"的补丁
★ 选型,就是在下面这 5 个维度之间做权衡(没有银弹):
┌────────────┬──────┬──────┬──────┬────────┐
│ 维度 │ CSR │ SSR │ SSG │ ISR │
├────────────┼──────┼──────┼──────┼────────┤
│ 首屏速度 │ 慢 │ 中 │ 极快 │ 极快 │
│ SEO 友好 │ 差 │ 好 │ 好 │ 好 │
│ 服务器成本 │ 极低 │ 高 │ 极低 │ 低 │
│ 数据新鲜度 │ 实时 │ 实时 │ 构建时│ 准实时 │
│ 构建耗时 │ 短 │ 短 │ 可能很长│ 中 │
└────────────┴──────┴──────┴──────┴────────┘
★★ 一个关键认知:同一个网站,不同页面【可以也应该】
用不同策略。首页用 SSG、商品页用 ISR、购物车用 SSR、
后台用 CSR —— 这才是成熟的工程做法,别一刀切。
渲染策略 1:CSR——浏览器里现搭舞台
# === ★ CSR(Client-Side Rendering):把渲染全交给浏览器 ===
# === ★ 它是怎么工作的 ===
# ★ 服务器返回的,是一个【几乎空的 HTML】 —— 通常就是
# 一个 加一个 JS bundle 的引用。
# ★ ★ 然后流程全在浏览器里发生:① 下载那个 JS;
# ② 执行 JS(React/Vue 启动);③ JS 再发请求去取
# 数据;④ 数据回来,JS 才把真正的 DOM 画出来。
# ★ 在第④步完成之前,用户看到的,是一片【白屏】或
# 一个 "Loading..."。
# === ★ 它的优点 ===
# ★ ① 服务器极轻:它只管把那几个静态文件(HTML/JS)
# 发出去,渲染这件累活一点不沾 —— 部署就是纯静态托管。
# ★ ② 渲染【之后】的交互体验极好:页面一旦"活"过来,
# 后续的切换、操作都在前端完成,不再请求整页(SPA)。
# === ★ 它的代价 ===
# ★ ① ★★ 首屏慢:用户要干等"下载 JS + 执行 + 取数据"
# 这一长串,才能看到第一眼内容。
# ★ ② ★★ SEO 差:搜索引擎爬虫抓到的,是那个【空壳
# HTML】 —— 里面没内容。(现代爬虫部分能执行 JS,
# 但不可靠、有延迟。)
# ★ ③ 低端设备 / 弱网下,JS 执行慢,白屏更久。
# === 小结 ===
# ★ CSR:服务器只给空壳,渲染全在浏览器、在用户打开
# 页面【之后】才发生。优点是服务器极轻、SPA 交互
# 流畅;代价是首屏白屏、SEO 差。★★ 适用场景:【不
# 需要 SEO】的页面 —— 登录后才能看的后台管理系统、
# 数据看板、Web 应用。这类页面用户是"登录进来用"的,
# 不靠搜索引擎导流,CSR 的短板根本不构成问题。
// ★ CSR:服务器只给空壳,浏览器下载 JS 后才取数、渲染
import { useState, useEffect } from 'react';
function ProductPage({ id }) {
const [data, setData] = useState(null);
// ★★ 关键:数据是在【浏览器里】、组件挂载【之后】才去取的
useEffect(() => {
fetch(`/api/product/${id}`)
.then(r => r.json())
.then(setData); // ★ 取到数据才有内容
}, [id]);
// ★★ 在数据回来之前,首屏用户看到的就是这一句 —— 白屏期
if (!data) return 'Loading...';
return data.name; // ★ 爬虫抓到的 HTML 里,没有这个
}
渲染策略 2:SSR——每次请求,服务器现做现端
# === ★ SSR(Server-Side Rendering):服务器现场渲染 ===
# === ★ 它是怎么工作的 ===
# ★ ★ 用户每发来一个请求,服务器就【当场】:跑一遍
# 组件、去数据源取最新数据、把页面拼成一份【完整
# 的、内容齐全的 HTML】,然后返回。
# ★ 浏览器收到的,不再是空壳,而是【一眼就能看的
# 完整页面】 —— 内容直接呈现,不用等 JS 取数。
# ★ (随后 JS 仍会加载、给页面"注入"交互能力,这一步
# 叫 hydration / 水合 —— 但用户【看】内容,不用等它。)
# === ★ 它的优点 ===
# ★ ① ★★ 首屏快:HTML 到达即有内容,白屏期被消掉。
# ★ ② ★★ SEO 好:爬虫抓到的就是内容齐全的 HTML。
# ★ ③ ★ 数据【实时】:每次请求都现取,用户看到的
# 永远是此刻最新的数据。
# === ★ 它的代价 ===
# ★ ① ★★ 服务器压力大:每一个请求,都要服务器实打
# 实地渲染一遍 —— 流量一大,CPU 就吃紧。
# ★ ② ★ TTFB(首字节时间)被数据源拖累:服务器要
# "等数据查完才能拼 HTML",数据库慢,整个页面就慢。
# ★ ③ ★ 需要一个【常驻的 Node 服务器】,不能像
# CSR/SSG 那样纯静态托管 —— 运维更重、成本更高。
# === 小结 ===
# ★ SSR:每次请求服务器都【现场】取数、渲染出完整
# HTML。优点是首屏快、SEO 好、数据实时;代价是
# 服务器每个请求都要干一遍渲染的活、压力大、成本高、
# TTFB 受数据源速度影响。★★ 适用场景:既【需要
# SEO】、内容又【高度个性化或必须实时】的页面 ——
# 每个用户看到的不一样(带登录态的首页、社交 feed),
# 或数据变得太快不容缓存(实时价格/库存的商品页)。
// ★ SSR:每次请求,服务器现场取数、渲染好完整 HTML 再返回
// (Next.js Pages Router 写法)
export async function getServerSideProps(context) {
const { id } = context.params;
// ★★ 这段代码在【服务器】上、在【每一次请求】到来时执行
const data = await db.queryProduct(id); // ★ 现查最新数据
return { props: { data } }; // ★ 数据随 HTML 一起送达
}
// ★ 组件收到的 data 已经填好 —— 浏览器拿到的就是完整页面
export default function ProductPage({ data }) {
return data.name; // ★★ 首屏直接有内容,爬虫也读得到
}
渲染策略 3:SSG——上线前就把页面全烤好
# === ★ SSG(Static Site Generation):构建时静态生成 ===
# === ★ 它是怎么工作的 ===
# ★ ★ 关键时机是【构建时】 —— 也就是你 build、准备
# 上线的那一刻。构建过程会跑一遍:为每一个页面取好
# 数据、渲染好,生成一个个【现成的 .html 文件】。
# ★ ★ 上线后,运行时的服务器(或干脆就是 CDN)做的事
# 极简单:用户来要哪个页,就把那个【早就烤好的
# .html】直接吐给他。没有任何"现场渲染"。
# === ★ 它的优点 ===
# ★ ① ★★ 首屏极快:CDN 直接给一个静态文件,这是
# 互联网上最快的一种响应,没有之一。
# ★ ② ★★ SEO 完美:就是一份内容齐全的静态 HTML。
# ★ ③ ★★ 服务器成本极低:纯静态托管,没有渲染开销。
# ★ ④ ★ 抗压:静态文件由 CDN 扛,流量再大也不怕。
# === ★ 它的代价 ===
# ★ ① ★★ 数据是【构建那一刻】的快照:页面里的内容,
# 永远停在你上次 build 的时间点。要更新,只能
# 【重新构建 + 重新部署】。
# ★ ② ★★ 页面极多时,构建时间会爆炸:几十万个页面
# 要在构建时一个个生成,一次全量 build 跑几十分钟
# 甚至几小时 —— 改一个错字都要重烤一遍,很痛苦。
# === 小结 ===
# ★ SSG:在【构建时】就把所有页面烤成静态 HTML,运行
# 时只是把现成文件吐出去。优点是首屏极快、SEO 完美、
# 服务器成本极低、极抗压;代价是数据被定格在构建那
# 一刻(要更新就得重新构建部署)、页面太多时构建
# 耗时爆炸。★★ 适用场景:内容【基本不变】的页面 ——
# 博客文章、技术文档站、营销落地页、公司官网。这类
# 内容更新频率以"周/月"计,重新构建的代价完全可接受。
// ★ SSG:构建时(上线前)就为每个页面生成好静态 HTML
// (Next.js Pages Router 写法)
export async function getStaticPaths() {
const ids = await db.allProductIds();
// ★ 告诉框架:构建时要为这些 id 各生成一个静态页
return { paths: ids.map(id => ({ params: { id } })), fallback: false };
}
export async function getStaticProps(context) {
// ★★ 这段代码【只在构建时跑一次】 —— 数据是构建那一刻的快照
const data = await db.queryProduct(context.params.id);
return { props: { data } };
}
// ★ 运行时,服务器/CDN 只是把构建好的 .html 直接吐出 —— 极快
export default function ProductPage({ data }) {
return data.name;
}
渲染策略 4:ISR——SSG 的"按需翻新"升级版
# === ★ ISR(Incremental Static Regeneration):增量静态再生 ===
# === ★ 它要解决的,是 SSG 的那个死穴 ===
# ★ SSG 又快又便宜,但有个致命伤:数据定格在构建时,
# 改内容必须【全量重新构建】。ISR 就是来补这个洞的。
# === ★★ 它是怎么工作的 ===
# ★ ISR 的页面,【仍然是静态的】、仍由构建生成。但你
# 给它多设一个东西:revalidate 时间(比如 60 秒)。
# ★ ★ 运行时的逻辑变成:
# ① 用户访问 -> 永远【先拿现成的那个静态页】(快);
# ② 框架同时检查:这个静态页"生成多久了"?
# ③ ★ 如果它已经超过了 revalidate 时间(过期了),
# 框架就在【后台悄悄】重新生成这【一个】页面;
# ④ ★★ 重新生成好之后,【下一个】访问者,拿到的就是
# 新版本了。
# ★ 一句话:ISR = 静态页 + 一个"过期后自动在后台
# 翻新自己"的能力。
# === ★ 还有"按需翻新"(on-demand revalidation)===
# ★ ★ 除了"定时过期",ISR 还支持【精准触发】:你的
# 内容一改(比如后台编辑了某篇文章),就调一个 API,
# 只把【那一个页面】立刻重新生成 —— 不用等它过期,
# 也不用重新构建整站。
# === ★ 它的优缺点 ===
# ★ 优点:① 几乎拿到了 SSG 的全部好处(快、便宜、
# 抗压);② 内容能保持"准实时"(最多陈旧一个
# revalidate 周期);③ 构建时不必生成全部页面 ——
# 冷门页可以等第一次被访问时再生成(fallback)。
# ★ 代价:① ★ 过期窗口内,撞上"第一个访问者"的人,
# 拿到的还是旧版本(他之后的人才新);② 需要一个
# 支持 ISR 的运行环境(Next.js + Node / Vercel 等)。
# === 小结 ===
# ★ ISR:页面仍是构建生成的静态页,但加一个 revalidate
# 时间 —— 访问者永远先拿现成静态页(快),页面一旦
# 超过 revalidate 就由框架【在后台悄悄重新生成】,
# 下一个访问者即拿到新版;还支持按需精准翻新某一页。
# ★★ 它兼得了 SSG 的速度成本和"内容能更新"。适用
# 场景:内容【会变、但不要求秒级实时】、且【页面量
# 大】 —— 电商商品/列表页、新闻资讯站、博客。
// ★ ISR:静态页 + 后台自动翻新 —— 只比 SSG 多写一个 revalidate
export async function getStaticProps(context) {
const data = await db.queryProduct(context.params.id);
return {
props: { data },
revalidate: 60, // ★★ 关键:这一个数字,把 SSG 变成了 ISR
}; // 含义:此页最多"陈旧"60 秒
}
// ★ 访问者永远先拿到现成静态页(快);若此页已超 60 秒,
// 框架在后台悄悄重新生成它,下一个访问者就拿到新版。
// ★ 按需翻新:内容一改,精准刷新这一个页(on-demand)
export default async function handler(req, res) {
await res.revalidate(`/product/${req.query.id}`); // ★ 只刷这一页
return res.json({ revalidated: true });
}
# 构建时(Next.js build)的输出 —— 一眼看出每个页面的渲染方式
Route Size Render
┌ ○ / 1.2 kB Static ← SSG 纯静态
├ ● /product/[id] 2.1 kB SSG ← 构建时生成
├ ◐ /product/[id] (ISR) 2.1 kB revalidate ← ISR 可再生
├ λ /cart 1.8 kB Server ← SSR 每次现渲染
└ ○ /admin 3.0 kB CSR 壳 ← 客户端渲染
符号:○ Static ● SSG ◐ ISR λ SSR/Server
★★ 同一个站,不同页面用不同策略 —— 这才是正解,别一刀切
工程选型:你的页面到底该用哪一种
# === ★ 选型不靠"哪个新潮",靠对页面问几个硬问题 ===
# === ★ 问题 1:这个页面,需要 SEO 吗 ===
# ★ ★ 这是第一个、也是最关键的分叉。
# - 【不需要】(登录后才能看的后台、Web 应用)
# -> 直接选 ★ CSR。服务器最轻、部署最简单,SEO
# 短板对这类页面根本不存在。选型到此结束。
# - 【需要】-> 继续往下问。
# === ★ 问题 2:这个页面的内容,多久变一次 ===
# ★ ★ 第二个分叉,问的是"数据新鲜度"。
# - 【几乎不变】(官网、文档、博客正文)
# -> 选 ★ SSG。又快又便宜,内容更新时重新构建即可。
# - 【会变,但能容忍几十秒到几分钟的延迟】
# -> 看问题 3。
# - 【必须秒级实时,或每个用户看到的都不同】
# -> 选 ★ SSR。只有它能保证"请求那一刻"的数据。
# === ★ 问题 3:页面数量大不大 ===
# ★ ★ 当内容"会变但能容忍延迟"时,用页面量来定:
# - 【页面很多】(几万、几十万个商品页)
# -> 选 ★ ISR。既不必构建时全量烤(构建会爆炸),
# 又能让内容保持准实时。
# - 【页面不多】
# -> SSG 也行 —— 内容一变就重新构建,几分钟的事。
# === ★ 别忘了:一个站,可以混用 ===
# ★ ★ 选型不是"给整个项目选一个",而是"给【每个
# 页面】选一个"。一个成熟的电商站,典型是:
# - 首页 / 活动页 -> SSG(基本不变,要最快)
# - 商品详情 / 列表 -> ISR(量大、会变、容忍延迟)
# - 购物车 / 结算 -> SSR(强个性化、要实时)
# - 用户后台 / 订单 -> CSR(登录后,不需要 SEO)
# === 认知 ===
# ★ 选型靠对页面问三个硬问题,不靠"哪个技术更新":
# ① 需不需要 SEO?不需要(登录后应用)-> CSR,选型
# 结束;② 需要 SEO,内容多久变一次?几乎不变 -> SSG,
# 必须实时或强个性化 -> SSR,会变但容忍延迟 -> 看③;
# ③ 页面量大不大?大 -> ISR(避免构建爆炸又保准实时),
# 小 -> SSG 也够。★★ 最重要的一条:选型是给【每个
# 页面】各选一个,同一个站首页 SSG、商品页 ISR、
# 购物车 SSR、后台 CSR 混用才是正解,别一刀切。
选型速查
页面类型 推荐策略 理由
=============================================================
后台管理 / 数据看板 CSR 登录后用,不需要 SEO
Web 应用(登录后) CSR 强交互 SPA,SEO 无所谓
公司官网 / 营销落地页 SSG 内容几乎不变,要最快
博客文章 / 技术文档 SSG 内容稳定,更新时重新构建
新闻资讯站 ISR 会更新、量大,容忍分钟级延迟
电商商品详情 / 列表 ISR 量大、价格库存会变但可缓存
社交 feed / 个性化首页 SSR 每个用户不同,要实时
购物车 / 结算页 SSR 强个性化,数据必须实时
搜索结果页 SSR 参数无穷,无法预生成
关键概念速查
-------------------------------------------------------------
CSR 渲染发生在:浏览器,用户打开页面之后
SSR 渲染发生在:服务器,每一次请求到来时
SSG 渲染发生在:构建机,上线前 build 那一刻
ISR 渲染发生在:构建机 + 运行时后台按需翻新
revalidate ISR 的过期时间,页面陈旧超过它就触发后台再生
hydration SSR/SSG 的 HTML 到达后,JS 再为它注入交互能力
TTFB 首字节时间,SSR 的它受数据源速度直接影响
口诀:先问需不需要 SEO,再问内容多久变,再问页面量多大
同一个站不同页面混用四种策略,才是工程上的正解
避坑清单
- CSR 不是落后的策略,登录后不需要 SEO 的后台和 Web 应用,CSR 就是最省事的正确选择
- 给一年更新不了几次的官网用 SSR 是浪费,每个访客都让服务器白白现渲染一遍,该用 SSG
- 给数据秒级变化的实时看板用 SSG,用户永远看到构建那一刻的老黄历,实时数据必须 SSR
- 几十万页面用纯 SSG,一次全量构建要跑几十分钟,改一个字都得重烤,这种量级该用 ISR
- SSR 的 TTFB 直接受数据源速度拖累,数据库一慢整个页面就慢,SSR 页面务必盯紧慢查询
- SSR 需要常驻 Node 服务器,不能像 CSR/SSG 那样纯静态托管,运维更重成本更高要算清
- ISR 的过期窗口内撞上第一个访问者的人仍拿到旧版本,对一致性敏感的场景要明白这一点
- ISR 的 revalidate 不是越短越好,太短等于退化成频繁重渲染,要按内容真实变化频率设
- 选型不是给整个项目选一个,而是给每个页面各选一个,一个站混用四种策略才是成熟做法
- 别被技术新旧绑架,ISR 出现最晚不代表最高级,选型唯一依据是这个页面的真实需求和代价
总结
这一趟把 CSR / SSR / SSG / ISR 彻底理清的过程,纠正了我一个关于"技术选型"的、藏得很深的错觉。在我过去的脑子里,这四个词之间,是有一条隐隐的"鄙视链"的——CSR 是最朴素、最"老"的;SSR 听起来就比 CSR"高级",因为它在服务器上做事;SSG 是后来的优化;而 ISR 是最新、最潮的那个,它出现得最晚,名字最长,文档里被讲得最起劲。我下意识地以为,越靠后出现的就越"先进",而"用更先进的",就约等于"做得更对"。所以我曾经的选型,与其说是"选型",不如说是"追新"——我倾向于用我知道的那个最新的策略,然后反过来说服自己,这个页面"正好适合"它。给一个一年也更新不了几次的官网上 SSR 时,我当时心里甚至还隐隐有点优越感。直到我被那些代价一一教育:被 SSR 白白拖垮的服务器、被 SSG 永远定格在构建时刻的实时看板、那个一次全量构建要烤四十分钟的电商——我才看清,这四种策略之间,根本【不存在】什么鄙视链。它们是四个【平级】的工具,各自解决的是不同的问题,各自付出的是不同的代价。复盘到最深,我意识到这四种策略真正的区别,根本不在"技术新旧",而在一个朴素到我从没认真想过的维度上:你把"渲染页面"这件事,放在了【时间轴】和【空间轴】的哪个点上。SSG 把它放在"上线前、构建机上";SSR 把它放在"用户请求的那一刻、服务器上";CSR 把它放在"更晚、用户的浏览器里";ISR 则是 SSG 加上了一个"事后还能在后台翻新"的补丁。它们用的可能是同一个 React、同一套组件代码——变的从来不是"用什么渲染",而是"在何时、在何地渲染"。而"何时、何地",恰恰是由这个页面的真实需求决定的:它的内容多久变一次?它要不要被搜索引擎读到?它的访客愿意为首屏多等几百毫秒吗?我又能为它的服务器付多少钱?这四个问题的答案,才是选型的唯一依据——不是哪个策略更新,不是文档里哪个被吹得更响。这个教训,我后来到处都看见它的影子:数据库选型时,我也曾因为某个 NoSQL"更新更潮"就想用它,而没先问自己"我的数据到底有没有强关系";架构上,我也曾被"微服务"这个词的光环吸引,而没掂量过自己的团队规模和业务复杂度配不配得上它的代价。技术世界里,"新"是一种极有迷惑性的东西——它太容易被人误读成"好"。这次最大的收获,是我给自己立了一条新规矩:面对任何一组可选的技术方案,我不再问"哪个更先进",我只逼自己回答两件事——这个页面(这件事)的真实需求,到底是什么?这个方案为了它的那些好处,要我付出的代价,我付得起、也愿意付吗?把"需求"和"代价"这两端摆正了,选型自然就清楚了,根本用不着什么鄙视链。SSR、SSG、ISR 这几个词教给我的,从来不是几个 API 的用法,而是一句更冷静的话:没有更高级的技术,只有更匹配的技术;你要选的,从来不是"最新的那个",而是"最懂你这个页面到底想要什么的那个"。
—— 别看了 · 2026