SSR 与 SSG
问题
什么是 CSR、SSR、SSG、ISR?它们分别解决什么问题?真实项目里应该怎么选?面试时如何把渲染流程、性能指标、Hydration、缓存和常见坑讲清楚?
什么是 CSR、SSR、SSG、ISR? 四种渲染方式区别在于「HTML 在哪生成、什么时候生成」:
- CSR:浏览器拿到空 HTML 壳,下载 JS 后才渲染(Vue/React 默认)。
- SSR:每次请求时服务器实时渲染出完整 HTML(
getServerSideProps、Next.jsdynamic)。 - SSG:构建时一次性把所有页面预渲染成静态 HTML,部署到 CDN(
getStaticProps、博客/文档站)。 - ISR:SSG 的升级版,构建时静态化 + 运行时按
revalidate时间增量更新(Next.js 的revalidate: 60)。
它们分别解决什么问题? 核心是在「首屏快、SEO、数据新鲜度、服务器成本」之间权衡:
- CSR 解决「前后端分离 + 服务器零成本」,但牺牲首屏和 SEO。
- SSR 解决「首屏白屏 + SEO + 个性化数据」,但 TTFB 升高、服务器压力大。
- SSG 解决「极致首屏 + 极低成本」,但内容更新要重新构建。
- ISR 是 SSG 痛点的解药——既享受静态化的速度,又能让数据按需更新。
真实项目里应该怎么选? 按场景套口诀就行:
- 要实时数据用 SSR:商品详情、个性化首页、登录后的内容。
- 内容稳定用 SSG:博客、文档、营销页、官网。
- 页面多 + 要更新用 ISR:电商品类页、新闻列表、CMS 驱动的内容。
- 强交互后台用 CSR:管理后台、低代码平台、在线编辑器。
- 其实 Next.js App Router 现在能混用——一个页面里 Server Components 走 SSR/SSG,互动部分用 Client Components 走 CSR。
面试时如何把渲染流程、性能指标、Hydration、缓存和常见坑讲清楚? 按这个顺序展开就抓得住要点:
- 渲染流程:画时序图——浏览器请求 → 服务端取数 + 渲染 → 返回 HTML → 浏览器展示 → 下载 JS → Hydration 让页面可交互。
- 性能指标:SSR/SSG 改善
TTFB(SSG 更好)/FCP/LCP,但TTI/INP仍取决于 JS 大小和 Hydration 成本。 - Hydration:服务端 HTML 和客户端首次渲染必须完全一致,否则报 hydration mismatch。常见坑是用了
Date.now()/Math.random()/window。 - 缓存:SSR 用 CDN 边缘缓存 +
Cache-Control、ISR 用增量重生成、SSG 直接 CDN 强缓存。 - 坑:
window is not defined(用useEffect或typeof window判断)、敏感数据泄漏到 props、并行请求避免串行 waterfall。
一句话概括
CSR 是浏览器拿到空壳后自己渲染,SSR 是每次请求时服务器现做 HTML,SSG 是构建时提前做好 HTML,ISR 是先静态化,再按规则增量更新。
记住一句面试口诀:
要实时,用 SSR;内容稳定,用 SSG;页面很多又要更新,用 ISR;强交互后台系统,用 CSR。
渲染方案讨论的不是“React/Vue 在哪里跑”这么简单,而是在权衡四件事:
- 首屏快不快:用户多久能看到内容
- 能不能 SEO:爬虫拿到的是内容还是空壳
- 数据新不新:页面是不是每次请求都拿最新数据
- 成本高不高:服务器、构建、缓存、复杂度谁来承担
四种渲染方式总览
| 方式 | HTML 什么时候生成 | 首屏内容 | SEO | 数据新鲜度 | 服务器压力 | 典型场景 |
|---|---|---|---|---|---|---|
| CSR | 浏览器运行时 | 慢,先空壳 | 差 | 取决于接口 | 低 | 后台系统、强交互应用 |
| SSR | 每次请求时 | 快,有内容 | 好 | 最新 | 高 | 搜索页、个性化页、实时价格 |
| SSG | 构建时 | 最快,有内容 | 好 | 构建时数据 | 极低 | 文档、博客、营销页 |
| ISR | 构建时 + 运行时增量 | 快,有内容 | 好 | 可接受延迟 | 中 | 商品详情、新闻、CMS 页面 |
更短的记法:
| 方案 | 像什么 |
|---|---|
| CSR | 到店后现点现做,还要自己等厨师开火 |
| SSR | 每来一桌客人,后厨现做一份 |
| SSG | 开店前把套餐都做好,客人来了直接端 |
| ISR | 先做好套餐,过一段时间发现旧了再后台换新 |
先分清几个容易混淆的指标
面试里最容易把“首屏快”“可交互快”“接口快”混成一团。渲染方案主要影响这些指标:
| 指标 | 含义 | 和渲染方式的关系 |
|---|---|---|
| TTFB | 浏览器收到第一个字节的时间 | SSR 往往更高,因为服务端要取数和渲染 |
| FCP | 页面首次绘制内容的时间 | SSR/SSG 通常优于 CSR |
| LCP | 最大内容块出现的时间 | SSR/SSG 有利于正文、标题、首图尽早出现 |
| TTI | 页面真正可交互的时间 | SSR/SSG 不一定快,仍然要下载 JS 并 Hydration |
| INP | 用户交互响应延迟 | JS 体积、Hydration、长任务都会影响 |
关键结论:
SSR/SSG 解决的是“更早看到内容”和“SEO”,但页面能不能点,还要看客户端 JS 下载、执行和 Hydration。
CSR:客户端渲染
CSR 的流程是:服务器先给一个很薄的 HTML,浏览器下载 JS,JS 执行后再请求数据,最后渲染页面。
<!-- CSR 初始 HTML 通常只有一个挂载点 -->
<div id="root"></div>
<script src="/assets/app.js"></script>
CSR 的优缺点
| 优点 | 缺点 |
|---|---|
| 部署简单,静态资源即可托管 | 首屏容易白屏 |
| 服务器压力小 | SEO 天然较差 |
| 前后端分离清晰 | 低端设备执行 JS 慢 |
| 适合复杂交互和登录后系统 | 首屏依赖 JS、接口、网络三件事 |
CSR 适合什么
- 后台管理系统
- 登录后才能看的用户中心
- 在线编辑器、低代码平台、地图类应用
- SEO 不重要、交互比首屏内容更重要的页面
SSR:服务端渲染
SSR 的流程是:浏览器请求页面,服务器取数据,把组件渲染成 HTML 返回。浏览器先展示 HTML,然后再下载 JS,执行 Hydration,让页面可交互。
SSR 解决了什么
SSR 最核心解决两个问题:
- 首屏白屏问题:浏览器不必等完整 JS 执行完才看到内容。
- SEO 问题:爬虫能直接拿到带内容的 HTML。
但 SSR 不等于“所有性能都更好”。它把一部分成本从浏览器挪到了服务器:
Next.js Pages Router 示例
// pages/products/[id].tsx
import type { GetServerSideProps } from 'next';
interface Product {
id: string;
name: string;
price: number;
}
export default function ProductPage({ product }: { product: Product }) {
return (
<main>
<h1>{product.name}</h1>
<p>价格:¥{product.price}</p>
</main>
);
}
export const getServerSideProps: GetServerSideProps = async ({ params }) => {
const response = await fetch(`https://api.example.com/products/${params!.id}`);
if (response.status === 404) {
return { notFound: true };
}
const product = await response.json();
return {
props: { product },
};
};
Next.js App Router 示例
// app/products/[id]/page.tsx
export const dynamic = 'force-dynamic';
export default async function ProductPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const product = await fetch(`https://api.example.com/products/${id}`, {
cache: 'no-store',
}).then(res => res.json());
return (
<main>
<h1>{product.name}</h1>
<p>价格:¥{product.price}</p>
</main>
);
}
SSR 的优缺点
| 优点 | 缺点 |
|---|---|
| 首屏有内容,FCP/LCP 通常更好 | TTFB 可能更高 |
| SEO 和社交分享友好 | 每次请求都可能消耗服务器资源 |
| 适合实时、个性化数据 | 缓存设计更复杂 |
| 可以在服务端安全读取 cookie、鉴权 | Hydration 成本仍然存在 |
SSR 常见坑
| 坑 | 原因 | 解决方式 |
|---|---|---|
window is not defined | 服务端没有浏览器对象 | 放进 useEffect,或判断 typeof window !== 'undefined' |
| Hydration mismatch | 服务端 HTML 和客户端首次渲染结果不一致 | 避免首屏直接用随机数、当前时间、浏览器环境差异 |
| TTFB 变慢 | 服务端串行请求、接口慢、渲染重 | 并行请求、缓存、Streaming、拆分慢组件 |
| 服务器压力大 | 每个请求都渲染 | CDN、页面缓存、接口缓存、边缘渲染 |
| 泄露敏感数据 | 把 token、内部字段塞进 props | 只返回客户端必要字段 |
SSG:静态站点生成
SSG 的流程是:构建时就把页面生成好,部署成 HTML、CSS、JS 等静态文件。用户访问时,CDN 直接返回 HTML。
SSG 为什么快
因为它把“取数据 + 渲染 HTML”的成本提前到了构建阶段。线上请求时不需要临时计算,只要读静态文件。
Next.js Pages Router 示例
// pages/posts/[slug].tsx
import type { GetStaticPaths, GetStaticProps } from 'next';
interface Post {
slug: string;
title: string;
content: string;
}
export default function PostPage({ post }: { post: Post }) {
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
export const getStaticPaths: GetStaticPaths = async () => {
const posts = await getAllPosts();
return {
paths: posts.map(post => ({
params: { slug: post.slug },
})),
fallback: false,
};
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
const post = await getPostBySlug(params!.slug as string);
return {
props: { post },
};
};
Next.js App Router 示例
// app/posts/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map(post => ({
slug: post.slug,
}));
}
export default async function PostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPostBySlug(slug);
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
SSG 的优缺点
| 优点 | 缺点 |
|---|---|
| 首屏非常快,TTFB 很低 | 内容更新通常要重新构建 |
| CDN 分发,高并发能力强 | 页面数量巨大时构建慢 |
| 服务器成本低,可用性高 | 不适合强实时、强个性化页面 |
| SEO 友好 | 构建期数据源不可用会影响发布 |
SSG 适合什么
- 文档站
- 博客
- 官网、营销页、活动页
- 内容更新频率低的 CMS 页面
- 商品详情中“允许分钟级延迟”的部分
ISR:增量静态再生
ISR 可以理解为:先按 SSG 返回静态页面,过期后触发后台重新生成。
它解决的是 SSG 的两个痛点:
- 页面太多,全部构建太慢。
- 内容会更新,但又不值得每次请求都 SSR。
ISR 的核心细节
| 细节 | 解释 |
|---|---|
| 不是到点自动更新 | 通常是过期后的下一次请求触发再生成 |
| 过期后先返回旧页面 | 用户不会因为再生成而一直等待,除非是首次 fallback blocking |
| 再生成失败怎么办 | 一般继续保留旧页面,避免线上直接坏掉 |
| 数据不是绝对实时 | 它追求的是“可接受延迟 + 静态性能” |
Next.js Pages Router 示例
export const getStaticProps: GetStaticProps = async ({ params }) => {
const product = await getProduct(params!.id as string);
return {
props: { product },
revalidate: 60,
};
};
export const getStaticPaths: GetStaticPaths = async () => {
const popularProducts = await getPopularProducts();
return {
paths: popularProducts.map(product => ({
params: { id: product.id },
})),
fallback: 'blocking',
};
};
fallback 三种模式
| 值 | 行为 | 适合场景 |
|---|---|---|
false | 未预生成路径直接 404 | 路径有限、必须提前确定 |
true | 先返回 fallback 页面,再客户端补齐 | 能接受骨架屏,页面体验要自己处理 |
'blocking' | 首次请求等待生成完成 | 更像 SSR,用户不会看到半成品 |
function ProductPage({ product }) {
const router = useRouter();
if (router.isFallback) {
return <ProductSkeleton />;
}
return <ProductDetail product={product} />;
}
On-Demand ISR
定时 revalidate 适合“最多旧 60 秒”这种场景。CMS 发布文章、商品改价这类事件,更适合按需触发。
// pages/api/revalidate.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.query.secret !== process.env.REVALIDATE_SECRET) {
return res.status(401).json({ message: 'Invalid token' });
}
try {
await res.revalidate(req.body.path);
return res.json({ revalidated: true });
} catch {
return res.status(500).json({ message: 'Revalidate failed' });
}
}
Next.js App Router 里的再验证
// 方式一:整页或 fetch 缓存按时间再验证
export const revalidate = 60;
export default async function Page() {
const product = await fetch('https://api.example.com/product/1', {
next: { revalidate: 60 },
}).then(res => res.json());
return <ProductDetail product={product} />;
}
// 方式二:按 tag 失效,适合 CMS Webhook
import { revalidateTag } from 'next/cache';
export async function POST() {
revalidateTag('products');
return Response.json({ ok: true });
}
Hydration:SSR/SSG 后为什么还要“激活”
SSR/SSG 返回的是 HTML,HTML 只能让用户“看到内容”。如果按钮要点击、输入框要响应、组件要有状态,客户端还需要下载 JS,把事件绑定回去,这个过程叫 Hydration。
Hydration 的本质
React 不会把 SSR 生成的 DOM 全部删掉重建,而是:
- 在客户端重新跑一遍组件逻辑,生成虚拟树。
- 尝试和已有 HTML 对齐。
- 给对应 DOM 绑定事件。
所以 Hydration 最怕的就是:服务端渲染结果和客户端第一次渲染结果不一样。
Hydration mismatch 常见来源
| 来源 | 示例 | 解决方式 |
|---|---|---|
| 时间 | new Date().toLocaleString() | 服务端传固定值,或客户端挂载后再显示 |
| 随机数 | Math.random() | 服务端生成并传入,或使用稳定 id |
| 浏览器 API | window.innerWidth | useEffect 中读取 |
| 用户本地状态 | localStorage.theme | 先渲染稳定默认值,挂载后更新 |
| 环境差异 | 服务端和客户端 locale 不一致 | 明确指定 locale/timeZone |
| 非法 HTML | p 里嵌 div | 修正 HTML 结构 |
// 不推荐:首屏服务端和客户端可能不一致
export function Time() {
return <span>{new Date().toLocaleString()}</span>;
}
// 推荐:先保持一致,挂载后再展示客户端时间
export function ClientTime() {
const [time, setTime] = useState('');
useEffect(() => {
setTime(new Date().toLocaleString());
}, []);
return <span>{time}</span>;
}
SSR 快,但 Hydration 可能拖后腿
SSR 页面可能出现这种情况:
这就是为什么 SSR 项目也要关心 JS 体积、代码分割、组件复杂度和长任务。
Streaming SSR 与选择性 Hydration
传统 SSR 常见问题是:服务器必须等所有数据都准备好,才能返回完整 HTML。只要一个慢接口卡住,整个页面都晚到。
Streaming SSR 的思路是:先把能渲染的部分流式发给浏览器,慢的部分用 fallback 占位,数据到了再继续推送。
import { Suspense } from 'react';
export default function Page() {
return (
<main>
<Header />
<Suspense fallback={<ProductSkeleton />}>
<Product />
</Suspense>
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews />
</Suspense>
</main>
);
}
React Server Components 和 Hydration 的关系
React Server Components(RSC)不是“传统 SSR 的同义词”。它的重点是:组件只在服务端执行,结果发给客户端,但组件代码本身不进客户端 bundle,也不需要 Hydration。
| 类型 | 在哪里执行 | 会进客户端 JS 吗 | 能用状态和事件吗 |
|---|---|---|---|
| Server Component | 服务端 | 不会 | 不能用 useState、onClick |
| Client Component | 服务端预渲染 + 客户端 Hydration | 会 | 可以 |
// Server Component:适合取数据、读文件、访问数据库
export default async function ProductInfo() {
const product = await db.product.findUnique({ where: { id: '1' } });
return <h1>{product.name}</h1>;
}
// Client Component:适合交互
'use client';
export function AddToCartButton() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>加入购物车</button>;
}
面试回答时可以这样说:
SSR 让 HTML 更早出现,RSC 进一步减少发到客户端的 JS;真正需要交互的部分才作为 Client Component Hydration。
缓存:SSR/SSG/ISR 的灵魂
渲染策略和缓存是绑在一起的。只讲 SSR/SSG,不讲缓存,面试官很容易继续追问。
常见缓存层
| 缓存层 | 缓存什么 | 常见用途 |
|---|---|---|
| CDN 缓存 | HTML、JS、CSS、图片 | SSG/ISR 静态资源分发 |
| 页面缓存 | 渲染后的 HTML/RSC payload | 减少 SSR 重复渲染 |
| 数据缓存 | 接口结果、数据库查询结果 | 减少慢接口和 DB 压力 |
| 浏览器缓存 | 静态资源 | 减少重复下载 |
SSR 页面能不能缓存
可以,但要看页面是否个性化。
| 页面类型 | 能否缓存 | 说明 |
|---|---|---|
| 全站一样的页面 | 可以强缓存或 CDN 缓存 | 更接近 SSG/ISR |
| 按地区不同 | 可以按地区维度缓存 | key 要包含地区 |
| 按登录用户不同 | 谨慎缓存 | key 必须包含用户维度,防止串数据 |
| 包含隐私信息 | 通常不缓存 HTML | 可以缓存公共接口数据 |
缓存最常见事故
| 事故 | 原因 |
|---|---|
| A 用户看到 B 用户信息 | 缓存 key 没带用户标识 |
| 商品价格迟迟不更新 | CDN、页面、接口多层缓存未统一失效 |
| 页面更新了但接口没更新 | 只 revalidate 页面,没处理数据缓存 |
| 构建后页面还是旧的 | 构建使用了旧数据源或缓存未清 |
真实项目怎么选
先不要背“SSR 好还是 SSG 好”,要按页面维度选。一个站点里通常会混用多种方式。
常见业务选择
| 场景 | 推荐 | 原因 |
|---|---|---|
| 文档站 | SSG | 内容稳定,CDN 极快 |
| 博客文章 | SSG/ISR | SEO 重要,更新不频繁 |
| 新闻详情 | ISR/SSR | 看实时性要求 |
| 新闻首页 | SSR/ISR | 更新频繁,SEO 重要 |
| 商品详情页 | ISR | 页面多,允许短暂延迟 |
| 商品价格/库存 | SSR 或客户端实时请求 | 数据敏感且变化快 |
| 搜索结果页 | SSR | query 多、内容动态、SEO 可能重要 |
| 用户订单页 | CSR/SSR | 登录态强,SEO 不重要 |
| 后台管理系统 | CSR | 强交互,SEO 不重要 |
| 活动页 | SSG | 静态资源和首屏体验优先 |
一个电商站的混合方案
面试时可以补一句:
渲染策略不是项目级选择,而是页面级甚至组件级选择。公共内容可以静态化,个性化和实时内容再动态获取。
面试高频追问
Q1:SSR 和 SSG 最大区别是什么?
答案:渲染时机不同。
| 对比 | SSR | SSG |
|---|---|---|
| 渲染时机 | 每次请求时 | 构建时 |
| 数据新鲜度 | 最新 | 构建时确定 |
| TTFB | 取决于服务端处理速度 | 通常很低 |
| 服务器压力 | 高 | 低 |
| 适用场景 | 动态、个性化、实时页面 | 静态、稳定、SEO 页面 |
一句话版:
SSR 是用户来了现做,SSG 是上线前做好。
Q2:SSR 一定比 CSR 快吗?
答案:不一定,要看“快”的定义。
- 如果说“更早看到内容”,SSR 通常更快。
- 如果说“页面可交互”,SSR 不一定更快,因为还要下载 JS 和 Hydration。
- 如果 SSR 接口很慢、服务器压力大,TTFB 甚至可能比 CSR 更差。
标准回答:
SSR 优化的是首屏内容和 SEO,不天然保证 TTI/INP。SSR 页面仍然要控制 JS 体积、减少 Hydration 成本、避免长任务。
Q3:SSR 为什么 SEO 更好?
CSR 初始 HTML 里通常只有一个空的 root,主要内容要等 JS 执行后才出现。SSR 返回的 HTML 已经包含标题、正文、结构化内容,搜索引擎和社交平台抓取更稳定。
<!-- CSR 爬虫可能先看到这个 -->
<div id="root"></div>
<!-- SSR/SSG 爬虫能直接看到这个 -->
<main>
<h1>商品标题</h1>
<p>商品描述</p>
</main>
Q4:什么是 Hydration?
Hydration 是客户端 JS 接管服务端 HTML 的过程。它会在客户端重新执行组件逻辑,和已有 DOM 对齐,然后绑定事件,让静态 HTML 变成可交互页面。
面试关键点:
- SSR/SSG 返回 HTML,只负责“可见”
- Hydration 后页面才“可交互”
- 服务端和客户端首次渲染不一致会导致 mismatch
- Hydration 成本过高会影响 TTI/INP
Q5:Hydration mismatch 怎么排查?
优先看这些:
- 首屏是否用了
Date.now()、new Date()、Math.random()。 - 是否在 render 阶段读取
window、document、localStorage。 - 服务端和客户端的语言、时区、环境变量是否不同。
- HTML 结构是否合法。
- 是否有组件根据屏幕宽度在首屏渲染不同结构。
解决思路:
- 保证首屏输出稳定
- 浏览器专属逻辑放到
useEffect - 服务端生成的数据通过 props 传给客户端
- 对不可避免的差异使用客户端挂载后再渲染
Q6:ISR 和 SSR 有什么区别?
| 对比 | ISR | SSR |
|---|---|---|
| 返回内容 | 多数时候返回缓存静态页 | 每次请求生成 |
| 数据实时性 | 有延迟 | 更实时 |
| 性能 | 接近 SSG | 取决于服务器和接口 |
| 成本 | 中等 | 较高 |
| 适合 | 可接受短暂旧数据的页面 | 必须实时或个性化页面 |
一句话版:
ISR 是“旧页面先顶上,后台慢慢换新”;SSR 是“每次都现场生成”。
Q7:ISR 到了 revalidate 时间会自动重新构建吗?
通常不是。更准确地说:
revalidate时间内,直接返回缓存页面。- 超过时间后,下一次请求仍可能先拿到旧页面。
- 框架在后台触发重新生成。
- 生成成功后,后续请求拿到新页面。
- 生成失败则继续保留旧页面。
所以 ISR 不是严格实时更新,而是“过期后按请求触发更新”。
Q8:SSR 如何做性能优化?
常见方向:
- 并行数据请求:避免接口瀑布流。
- 缓存接口数据:慢数据不要每次都查库。
- 缓存页面结果:对非个性化 SSR 页面做 CDN/页面缓存。
- Streaming SSR:慢模块用
Suspense拆开,不阻塞整页。 - 减少客户端 JS:代码分割、RSC、懒加载重组件。
- 边缘渲染:把渲染放到离用户更近的位置。
- 监控 TTFB/LCP/INP:别只看服务端日志。
// 避免串行
const user = await getUser();
const posts = await getPosts();
// 推荐并行
const [user, posts] = await Promise.all([
getUser(),
getPosts(),
]);
Q9:SSR 有哪些安全风险?
| 风险 | 说明 |
|---|---|
| 敏感数据泄露 | 服务端 props 被序列化到 HTML 里,客户端能看到 |
| 缓存串用户 | CDN 或页面缓存没有区分 cookie/user |
| XSS | 服务端拼接 HTML 或使用 dangerouslySetInnerHTML |
| 内部接口暴露 | 把内部错误、token、debug 信息返回到页面 |
最重要的一句:
服务端能拿到的数据,不代表都能传给客户端。
Q10:Next.js App Router 里怎么判断静态还是动态?
粗略理解:
- 没有动态请求信息、没有禁用缓存的数据请求,更容易静态化。
- 使用
cookies()、headers()、searchParams、cache: 'no-store'、dynamic = 'force-dynamic'等,通常会让页面变动态。 - 使用
revalidate或fetch(..., { next: { revalidate } })可以做类似 ISR 的缓存再验证。
// 静态或可再验证
export const revalidate = 3600;
// 强制动态
export const dynamic = 'force-dynamic';
// 单个请求不缓存
await fetch(url, { cache: 'no-store' });
Q11:为什么 SSG 页面很多时会有问题?
因为所有静态路径都要在构建阶段生成。页面数量从 1 千涨到 100 万时,构建时间、构建内存、数据源压力、部署产物体积都会变成问题。
解决方式:
- 热门页面预生成
- 长尾页面用 fallback/按需生成
- 用 ISR 分批更新
- 拆分站点或拆分构建任务
- 对内容源做缓存和限流
Q12:服务端渲染和同构渲染是一回事吗?
不完全是。
- 服务端渲染:强调 HTML 在服务端生成。
- 同构渲染:强调同一套组件代码既能在服务端渲染,也能在客户端运行并接管。
React/Vue 的 SSR 通常都是同构渲染:服务端先渲染 HTML,客户端再 Hydration。
最后用一张表记住
| 你听到的需求 | 第一反应 |
|---|---|
| “SEO 很重要” | SSR / SSG / ISR |
| “必须实时展示最新数据” | SSR |
| “内容基本不变” | SSG |
| “页面很多,内容偶尔更新” | ISR |
| “登录后后台系统” | CSR |
| “首屏要快,但按钮也要尽快能点” | SSR/SSG + 减少 JS + 优化 Hydration |
| “爬虫和社交分享要稳定” | SSR/SSG |
| “不能让用户看到旧价格” | SSR 或客户端实时请求关键字段 |
| “访问量巨大,内容可缓存” | SSG/ISR + CDN |
面试回答模板
可以按这个顺序说,基本能覆盖大多数追问:
- 先定义:CSR 浏览器渲染,SSR 请求时渲染,SSG 构建时渲染,ISR 静态页过期后增量更新。
- 讲流程:SSR 返回 HTML 后还要 Hydration,SSG/ISR 主要靠 CDN 返回静态 HTML。
- 讲取舍:SSR 数据新但服务器压力大,SSG 快但更新慢,ISR 折中,CSR 适合强交互后台。
- 讲指标:SSR/SSG 改善 FCP/LCP/SEO,但 TTI/INP 还受 JS 和 Hydration 影响。
- 讲实践:按页面选型,公共内容静态化,个性化/实时内容动态化,缓存 key 和失效策略要设计好。