CSR · SSR · SSG · ISR
四大渲染策略全解
从「纯静态」到「智能混合」的演进之路
Web 渲染不是一道单选题。每种策略都是在速度、新鲜度、SEO、交互性之间做权衡。理解痛点,才能选对方案。
速度痛点
CSR 白屏 3 秒 = 53% 用户流失(Google 数据)
SEO 痛点
CSR 页面的搜索引擎可见内容为零字节
成本痛点
SSR 每请求都需服务端计算,10K QPS = 巨额账单
选择一种策略,观察它的运作方式
点击下方标签切换不同渲染策略,观察数据流、性能指标和对应代码的差异。
Server-Side Rendering
🍽️你点单后厨房现做,等一会儿上热菜
电商商品页、搜索结果页、个性化内容页
数据流
性能指标
关键评分
首屏即完整 HTML,SEO 友好
数据实时,每次请求都是最新
支持 Streaming SSR 渐进式渲染
TTFB 受服务器响应速度影响
每次请求都要服务端计算,服务器压力大
需要 Node.js 服务器,不能纯 CDN 部署
Next.js 实现
// app/products/[id]/page.tsx
// 不需要 "use client" — 默认是服务端组件 (Server Component)
async function getProduct(id: string) {
const res = await fetch(
`https://api.store.com/products/${id}`,
{ cache: "no-store" } // ← 关键:禁止缓存,每次请求都获取最新数据
);
if (!res.ok) throw new Error("Product not found");
return res.json();
}
export default async function ProductPage({
params,
}: {
params: { id: string };
}) {
const product = await getProduct(params.id);
// ✅ 服务端完成 HTML 渲染 → 浏览器收到即可见
return (
<article>
<h1>{product.name}</h1>
<p className="text-2xl font-bold">¥{product.price}</p>
<p>{product.description}</p>
</article>
);
}亲手体验 ISR 的缓存与重新生成
调整 revalidate 参数,观察缓存过期和后台重新生成的全过程。
ISR 实验场
拖动滑块设置 revalidate 周期,观察页面缓存行为
访问日志
点击「访问页面」模拟用户请求
💡 观察要点:当倒计时归零后访问页面,ISR 会先返回旧缓存(用户无感知),同时后台重新生成。新页面会在下次请求时生效——这就是Stale-While-Revalidate 策略。
进阶模式:Streaming & 并行请求
掌握基础策略后,这两个模式能让你的 SSR 性能再上一个台阶。
Streaming SSR
用 Suspense 实现渐进式渲染
核心思路:将慢速数据源用 <Suspense> 包裹。快速部分(如用户头像)先发送到浏览器,慢速部分(如分析图表)完成后再流式追加。
❌ 无 Streaming
TTFB = 50 + 2000 = 2050ms
✅ 有 Streaming
TTFB = 50ms(图表后到)
// app/dashboard/page.tsx
import { Suspense } from "react";
// 快速数据源(~50ms)
async function UserHeader() {
const user = await fetch("/api/user", {
cache: "no-store",
}).then(r => r.json());
return <header>👋 欢迎, {user.name}</header>;
}
// 慢速数据源(~2000ms)
async function AnalyticsChart() {
const data = await fetch("/api/analytics", {
cache: "no-store",
}).then(r => r.json());
return <Chart data={data} />;
}
export default function Dashboard() {
return (
<div>
{/* ✅ 先渲染,用户立刻看到 */}
<UserHeader />
{/* 慢速部分用 Suspense 包裹,不阻塞上面的渲染 */}
<Suspense fallback={<ChartSkeleton />}>
<AnalyticsChart />
</Suspense>
{/* ↑ 浏览器收到 UserHeader 的 HTML 后立即显示
AnalyticsChart 加载完成后以流式追加到页面 */}
</div>
);
}并行数据获取
用 Promise.all 消灭串行等待
核心问题:在服务端组件中写多个 await fetch() 时,如果顺序执行(串行),总耗时是所有请求之和。改为并行后,总耗时 = 最慢请求的时间。
❌ 串行
300 + 200 + 150 = 650ms
✅ 并行
max(300,200,150) = 300ms
// ❌ 反模式:串行请求 — 总耗时 300+200+150 = 650ms
async function getPageData_Slow() {
const user = await fetchUser(); // 300ms ← 等完才继续
const posts = await fetchPosts(); // 200ms
const ads = await fetchAds(); // 150ms
return { user, posts, ads };
}
// ✅ 正确:并行请求 — 总耗时 = max(300,200,150) = 300ms
async function getPageData_Fast() {
const [user, posts, ads] = await Promise.all([
fetchUser(), // ← 三个请求同时发出
fetchPosts(),
fetchAds(),
]);
return { user, posts, ads };
}Next.js App Router — Fetch 选项速查
cache: "no-store"
每次请求都获取最新数据
cache: "force-cache"
构建时缓存,后续复用
next: { revalidate: 60 }60 秒后过期,后台重新生成
"use client" + useEffect
浏览器端发起请求
决策框架与生产级考量
掌握「什么时候用什么」比「怎么用」更重要。
渲染策略决策树
🔍 你的页面需要被搜索引擎收录吗?
✅ 需要 SEO
数据多久更新一次?
❌ 不需要 SEO
页面交互需求如何?
💡 同一应用中可以混合使用多种策略。例如:首页用 ISR、商品详情用 SSR、用户后台用 CSR。
性能与特性对比矩阵
| 指标 | CSR | SSR | SSG | ISR |
|---|---|---|---|---|
| TTFB | ~50ms ⚡ | 200~500ms | ~30ms ⚡ | ~30ms ⚡ |
| FCP | 1~3s 🐢 | 300~800ms | 100~300ms | 100~300ms |
| TTI | 2~5s 🐢 | 500~1000ms | 200~500ms | 200~500ms |
| SEO | ❌ 差 | ✅ 优秀 | ✅ 优秀 | ✅ 优秀 |
| 数据新鲜度 | ✅ 实时 | ✅ 实时 | ❌ 构建时 | ✅ 可配置 |
| 服务器成本 | ✅ 低 | ❌ 高 | ✅ 零 | ✅ 极低 |
| Hydration | 完整客户端 | 需要 | 需要 | 需要 |
常见陷阱与反模式
🚫 内容页用 CSR
博客、电商产品页使用 CSR("use client" + useEffect 请求)
为什么错:搜索引擎爬虫拿到空 HTML,Lighthouse SEO 0分。首屏白屏 3 秒 = 53% 用户流失。
博客用 SSG(generateStaticParams),产品页用 SSR(cache: 'no-store')或 ISR。
🚫 静态页用 SSR
"关于我们"、"服务条款"等几乎不变的页面使用 SSR
为什么错:每个请求都要服务端计算,浪费资源。10000 QPS 的 SSR 成本是 SSG 的 100 倍以上。
纯静态页面使用 SSG,Next.js 中默认行为就是 SSG。
🚫 ISR 时间设太短
新闻网站设置 revalidate: 1(1秒)
为什么错:revalidate 过短 ≈ SSR,失去了 ISR 的缓存优势。且分布式场景下可能导致频繁重建。
根据内容更新频率设置:新闻用 60~300s,产品列表用 600~3600s。
🚫 SSR 中串行请求
在 Server Component 中顺序 await 多个 fetch
为什么错:3 个 300ms 的请求串行 = 900ms TTFB。用户在白屏中等待。
使用 Promise.all() 并行,或用 Suspense 流式渲染。总耗时降至 300ms。
渲染策略 Cheat Sheet
每次请求获取最新数据,不做任何缓存
fetch(url, { cache: "no-store" })构建时缓存结果,后续复用
fetch(url, { cache: "force-cache" })指定秒数后过期,下次访问触发后台重新生成
fetch(url, { next: { revalidate: 60 } })组件在浏览器端渲染和执行
// 文件顶部添加
"use client";构建时生成所有动态路由的静态页面
export async function generateStaticParams() {...}将慢速部分异步流式渲染,不阻塞快速部分
<Suspense fallback={<Skeleton />}>
<SlowComponent />
</Suspense>一句话记住四种策略
CSR
浏览器自己做菜——慢但灵活
SSR
服务器现做——快但费厨师
SSG
提前做好放冰箱——极速但可能不新鲜
ISR
自动补货的冰箱——极速且保持新鲜
🎯 现代 Web 开发的黄金法则:没有银弹。同一应用中混合使用多种策略,按页面粒度选择最优方案。