渲染策略

CSR · SSR · SSG · ISR
四大渲染策略全解

选择渲染策略就像选择餐厅的出餐方式:是现做、预做、还是自助? 不同场景需要不同的策略,理解它们的差异是构建高性能 Web 应用的基石。

CSR

Client-Side Rendering

给你食材和菜谱,自己动手在餐桌上做

SSR

Server-Side Rendering

你点单后厨房现做,等一会儿上热菜

SSG

Static Site Generation

餐厅早上预做好热门菜品,来了直接端上桌

ISR

Incremental Static Regeneration

自动补货的自助餐——定期更新菜品,不会断供

L2 · 为什么需要?

从「纯静态」到「智能混合」的演进之路

Web 渲染不是一道单选题。每种策略都是在速度、新鲜度、SEO、交互性之间做权衡。理解痛点,才能选对方案。

1990s — 静态时代

纯 HTML 文件

每个页面都是提前写好的 .html 文件,速度极快但无法动态更新。每次改内容都需要手动上传文件。

2000s — 服务端时代

PHP/JSP 服务端渲染

服务器收到请求后执行代码、查数据库、拼 HTML。每次请求都重复整个过程——100 个并发就要算 100 次。

2010s — 客户端革命

SPA / CSR 大行其道

React/Vue 兴起,浏览器接管一切。交互丝滑了,但首屏白屏 3 秒、SEO 全军覆没。Lighthouse SEO 评分经常 0/100。

2020s — 混合渲染

SSG / ISR / Streaming SSR

Next.js 等框架将选择权交给开发者:同一应用中可以混合使用多种策略,按页面粒度选择最优方案。

速度痛点

CSR 白屏 3 秒 = 53% 用户流失(Google 数据)

SEO 痛点

CSR 页面的搜索引擎可见内容为零字节

成本痛点

SSR 每请求都需服务端计算,10K QPS = 巨额账单

L3 · 核心原理

选择一种策略,观察它的运作方式

点击下方标签切换不同渲染策略,观察数据流、性能指标和对应代码的差异。

Server-Side Rendering

🍽️你点单后厨房现做,等一会儿上热菜

适用场景

电商商品页、搜索结果页、个性化内容页

数据流

浏览器发起请求
服务器接收
服务端请求 API200~500ms
服务端渲染 HTML
返回完整 HTML
客户端 Hydrate

性能指标

TTFB
🐢
FCP
TTI
TTFB = 首字节时间FCP = 首次内容绘制TTI = 可交互时间

关键评分

SEO
🐢
新鲜度
🐢
✅ 优势

首屏即完整 HTML,SEO 友好

数据实时,每次请求都是最新

支持 Streaming SSR 渐进式渲染

⚠️ 劣势

TTFB 受服务器响应速度影响

每次请求都要服务端计算,服务器压力大

需要 Node.js 服务器,不能纯 CDN 部署

Next.js 实现

products/[id]/page.tsx — SSRtypescript
// 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 周期,观察页面缓存行为

revalidate15s
15
✅ 页面新鲜 — 缓存有效

访问日志

点击「访问页面」模拟用户请求

💡 观察要点:当倒计时归零后访问页面,ISR 会先返回旧缓存(用户无感知),同时后台重新生成。新页面会在下次请求时生效——这就是Stale-While-Revalidate 策略。

L4 · 代码实战

进阶模式:Streaming & 并行请求

掌握基础策略后,这两个模式能让你的 SSR 性能再上一个台阶。

Streaming SSR

用 Suspense 实现渐进式渲染

核心思路:将慢速数据源用 <Suspense> 包裹。快速部分(如用户头像)先发送到浏览器,慢速部分(如分析图表)完成后再流式追加。

❌ 无 Streaming

TTFB = 50 + 2000 = 2050ms

✅ 有 Streaming

TTFB = 50ms(图表后到)

dashboard/page.tsx — Streaming SSRtsx
// 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

串行 vs 并行数据获取typescript
// ❌ 反模式:串行请求 — 总耗时 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 选项速查

SSR
cache: "no-store"

每次请求都获取最新数据

SSG
cache: "force-cache"

构建时缓存,后续复用

ISR
next: { revalidate: 60 }

60 秒后过期,后台重新生成

CSR
"use client" + useEffect

浏览器端发起请求

L5 · 工程全景

决策框架与生产级考量

掌握「什么时候用什么」比「怎么用」更重要。

渲染策略决策树

🔍 你的页面需要被搜索引擎收录吗?

✅ 需要 SEO

数据多久更新一次?

实时 / 每次请求最新 SSR + Streaming
🕐分钟/小时间隔 ISR
📄很少 / 从不更新 SSG

❌ 不需要 SEO

页面交互需求如何?

🎯交互密集(仪表盘、编辑器)→ CSR
📊需要最新数据(实时面板)→ SSR

💡 同一应用中可以混合使用多种策略。例如:首页用 ISR、商品详情用 SSR、用户后台用 CSR。

性能与特性对比矩阵

指标
CSR
SSR
SSG
ISR
TTFB~50ms ⚡200~500ms~30ms ⚡~30ms ⚡
FCP1~3s 🐢300~800ms100~300ms100~300ms
TTI2~5s 🐢500~1000ms200~500ms200~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

cache: 'no-store'强制 SSR

每次请求获取最新数据,不做任何缓存

fetch(url, { cache: "no-store" })
cache: 'force-cache'强制 SSG

构建时缓存结果,后续复用

fetch(url, { cache: "force-cache" })
next: { revalidate }ISR

指定秒数后过期,下次访问触发后台重新生成

fetch(url, { next: { revalidate: 60 } })
"use client"标记 CSR

组件在浏览器端渲染和执行

// 文件顶部添加
"use client";
generateStaticParamsSSG 路由预生成

构建时生成所有动态路由的静态页面

export async function generateStaticParams() {...}
<Suspense>Streaming SSR

将慢速部分异步流式渲染,不阻塞快速部分

<Suspense fallback={<Skeleton />}>
  <SlowComponent />
</Suspense>

一句话记住四种策略

🧑‍🍳

CSR

浏览器自己做菜——慢但灵活

👨‍🍳

SSR

服务器现做——快但费厨师

🍱

SSG

提前做好放冰箱——极速但可能不新鲜

🔄🍱

ISR

自动补货的冰箱——极速且保持新鲜

🎯 现代 Web 开发的黄金法则:没有银弹。同一应用中混合使用多种策略,按页面粒度选择最优方案。