# next渲染策略与缓存机制
# 为什么 next 的渲染策略是核心能力
Next 真正强的点不是“能 SSR”,而是可以在同一项目里组合多种策略,并通过缓存体系把性能和一致性做平衡。
如果你是资深前端,建议把 Next 的性能模型拆成三层看:
- 渲染层:SSR/SSG/ISR/Streaming
- 缓存层:请求缓存、页面缓存、CDN 缓存
- 失效层:时间失效、事件失效、标签失效
# 四种常见渲染形态
# 1)CSR(客户端渲染)
首屏壳子在浏览器里再拉数据。
适合强交互后台,不适合 SEO 首屏要求高的内容页。
# 2)SSR(服务端请求时渲染)
每次请求都在服务端算页面,数据新鲜度高。
代价是服务端计算成本更高。
# 3)SSG(构建时静态生成)
构建阶段把页面提前生成静态文件。
访问快、成本低,适合文档、营销页。
# 4)ISR(增量静态再生成)
首次静态,过期后后台重建,兼顾性能与新鲜度。
# 渲染策略选择矩阵(生产决策版)
# 内容型页面(新闻/博客/文档)
- 推荐:
SSG + ISR - 目标:高命中缓存、低服务端成本
# 交易型页面(订单、库存、价格)
- 推荐:
SSR + 局部缓存 - 目标:数据新鲜优先,避免陈旧业务数据
# 强交互后台(报表、管理台)
- 推荐:
CSR/SSR 混合 - 目标:交互体验与权限控制优先
# 混合页面(电商详情)
- 推荐:
Server Components + Suspense + 局部 Client - 目标:首屏快、交互区灵活、可控新鲜度
# App Router 下的数据缓存语义
fetch 在服务端默认可缓存(取决于配置),常见参数:
await fetch(url, {
next: { revalidate: 60, tags: ["post-list"] },
});
2
3
# 关键点
revalidate:按时间重验证tags:按标签失效(配合revalidateTag)cache:控制请求缓存行为
# cache 常见模式(重要)
force-cache:尽量走缓存(内容站常用)no-store:每次都拉新(强实时业务)next.revalidate:在缓存基础上给出再验证窗口
# 你需要理解的“多层缓存”
很多同学把 Next 缓存只看成 fetch 缓存,实际上至少有三层:
- 数据缓存(fetch 层)
- 路由输出缓存(页面/片段层)
- 边缘/CDN 缓存(分发层)
线上问题通常出在“层间不一致”,不是某一层配置错了。
# 一个常见策略组合(内容站)
- 首页列表:
revalidate: 60 - 详情页:
revalidate: 300 - 后台发布后:调用
revalidateTag("post-list")
收益:
- 用户看的是近实时数据
- 服务端不必每次都全量重算
# 失效策略设计:时间驱动 vs 事件驱动
# 时间驱动(TTL)
适合“允许短时间旧数据”的场景,如资讯列表。
优点是简单稳定,缺点是存在窗口期。
# 事件驱动(Tag/Path Revalidate)
内容发布、价格变更后主动触发失效。
优点是更实时,缺点是需要保证发布链路可靠触发。
# 生产建议
- 列表页用
revalidate + tag组合 - 关键详情页可缩短
revalidate并叠加事件失效 - 写操作完成后在同事务语义里触发 revalidate,避免“写成功但缓存未失效”
# 动态与静态边界判断
当你使用如下能力时,路由更可能走动态渲染:
cookies()/headers()- 与用户会话强绑定的数据
- 明确设置
dynamic = "force-dynamic"
建议:把“必须动态”的逻辑隔离到最小范围,避免整页退化为动态。
# Streaming 与缓存的配合
很多页面慢不是“SSR 慢”,而是“等待所有数据再输出”。
配合 Suspense 分段输出后,即使某块慢数据未返回,首屏壳和核心信息也可先到达。
建议把页面拆成:
- 首屏关键区:快接口 + 稳定缓存
- 次要区块:慢接口 + Suspense fallback
这样可以同时优化 TTFB 和 FCP 体感。
# 案例:文章列表页(带缓存与重验证)
app/posts/page.tsx
async function getPosts() {
const res = await fetch("https://api.example.com/posts", {
next: { revalidate: 60, tags: ["posts"] },
});
return res.json();
}
export default async function Page() {
const posts = await getPosts();
return (
<ul>
{posts.map((p: any) => (
<li key={p.id}>{p.title}</li>
))}
</ul>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
发布后在服务端触发:
import { revalidateTag } from "next/cache";
export async function POST() {
revalidateTag("posts");
return Response.json({ ok: true });
}
2
3
4
5
6
# 进阶:按租户隔离缓存标签
多租户场景不要只用 "posts",建议标签带租户前缀:
const tag = `tenant:${tenantId}:posts`;
否则会出现租户 A 发布导致租户 B 缓存被误失效。
# 案例:商品详情(价格实时 + 详情缓存)
需求:
- 商品描述可以 5 分钟缓存
- 价格库存必须实时
实现思路:
- 描述信息:
revalidate: 300 - 价格库存:
cache: "no-store" - UI 上用 Suspense 把价格区块独立,避免拖慢整页输出
这类“冷热数据分治”是 Next 项目最常见性能抓手。
# 观测与压测建议(别只看 Lighthouse)
至少监控:
- TTFB(服务端首字节)
- HTML 大小与 RSC Payload 大小
- 缓存命中率(数据层、CDN 层)
- 页面级 revalidate 触发次数
压测时要做两类流量:
- 冷启动流量(无缓存)
- 稳态流量(高缓存命中)
两者差异能直接反映你缓存策略是否有效。
# 常见误区
- 误区 1:所有页面都 SSR 才“高级”
- 误区 2:看见缓存就担心脏数据,不做失效策略
- 误区 3:把用户态数据和公共数据混用同一缓存策略
- 误区 4:只在开发环境验证缓存行为(dev 与 prod 行为不同)
- 误区 5:把 revalidate 当实时系统替代方案
# 小结
Next 渲染策略的正确打开方式是“组合拳”:
静态优先 + 动态兜底 + 标签化失效。
这比单纯讨论 SSR/CSR 更接近真实生产场景。