渲染哲学
本教程共 42 篇 · 第 38 篇 · 更新于 2026-07-30 · 约 7 分钟阅读
38. 渲染哲学
本节目标:理解 Next.js 独特的组件级渲染模型,掌握不同渲染方式的权衡,学会为场景选择最合适的渲染策略。
静态与动态是光谱
大多数框架在路由层面做静态/动态的二选一:一个页面要么构建时生成,要么每次请求时渲染。
Next.js 不一样。静态和动态的边界在组件级别,不在路由级别。 同一个页面可以同时包含:
- 立即渲染的静态外壳
- 独立缓存的函数
- 流式传输的动态内容
这让开发者可以精细控制每个部分的渲染行为,而不是被迫做非此即彼的选择。
渲染方式对比
SSG(Static Site Generation)
构建时生成 HTML,每次请求直接返回同一个文件。
优点:加载最快、CDN 友好、托管成本低 缺点:内容更新需要重新构建
适合:营销页面、文档、博客文章
SSR(Server-Side Rendering)
每次请求时在服务端生成 HTML。
优点:内容总是最新的、可以访问请求信息 缺点:服务器压力大、响应速度依赖服务端性能
适合:个性化页面、实时数据、依赖 cookies/headers 的页面
ISR(Incremental Static Regeneration)
构建时生成静态页面,但可以在运行时按需更新。
优点:兼顾静态页面的速度和动态内容的灵活性 缺点:首次访问可能拿到过期数据
适合:电商产品列表、新闻网站、大型内容站点
CSR(Client-Side Rendering)
服务端返回空 HTML,由 JavaScript 在浏览器中渲染。
优点:服务器压力最小、交互体验流畅 缺点:首屏加载慢、SEO 不友好
适合:后台管理系统、高度交互的应用
ISR 实战
基于时间的重新验证
// app/blog/[id]/page.tsx
import { cacheLife } from 'next/cache'
export default async function Page({ params }) {
const { id } = await params
const post = await getPost(id)
return <article>{post.content}</article>
}
async function getPost(id: string) {
'use cache'
cacheLife('minutes') // 1 分钟后标记为过期
return fetch(`https://api.example.com/posts/${id}`).then(r => r.json())
}
NoteNext.js 16 启用 Cache Components 后,
export const revalidate = 60等路由段配置已被移除。请使用cacheLife配合"use cache"指令实现基于时间的重新验证。
工作流程:
- 构建时生成所有已知页面的静态版本
- 请求到来时直接返回缓存(瞬间响应)
- 60 秒后,下一个请求仍返回过期缓存
- 后台开始重新生成新版本
- 生成完成后替换缓存
按需重新验证
用 revalidatePath 或 revalidateTag 主动触发更新:
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function publishPost(id: string) {
await db.posts.update(id, { published: true })
revalidatePath(`/posts/${id}`) // 立即重新验证
}
// 给 fetch 打标签
const data = await fetch('https://api.example.com/posts', {
next: { tags: ['posts'] },
})
// 按标签重新验证
revalidateTag('posts', 'max')
组件级渲染模型
三种渲染模型的对比
| 模型 | 静态/动态边界 | 灵活性 | 基础设施复杂度 |
|---|---|---|---|
| 构建时预渲染 | 页面 | 低 | 低 |
| 路由级边界 | 路由 | 中 | 中 |
| 组件级边界 | 组件 | 高 | 高 |
Next.js 选择了组件级边界,这意味着:
- 流式传输:静态和动态内容在同一个响应中发送
- 缓存协调:多个实例之间需要同步缓存失效
- 缓存一致性:HTML 和 RSC Payload 必须保持同步
基础设施要求
组件级渲染模型对托管平台有要求:
- 流式传输:服务器必须支持分块发送响应
- 缓存协调:多实例部署时需要共享缓存失效信息
- CDN 集成:PPR 的静态外壳需要 CDN 能存储并恢复动态渲染
流式渲染
Next.js 用流式 SSR 来优化用户体验。服务器先发送页面的静态部分,动态内容准备好后再流式传输。
import { Suspense } from 'react'
export default function Page() {
return (
<div>
<header>立即渲染的头部</header>
<Suspense fallback={<div>加载中...</div>}>
<DynamicContent /> {/* 流式传输 */}
</Suspense>
</div>
)
}
用户不用等所有内容都准备好,先看到页面框架,再逐步填充数据。
Partial Prerendering (PPR)
PPR 是 Next.js 16 的新模式,把页面分成静态外壳和动态部分:
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig
- 静态外壳:包含导航、布局等,可以预渲染和缓存
- 动态部分:包含个性化数据,流式传输
PPR 在 Next.js 16 中的工作方式与 15 的 canary 版本不同。如果你在用 15 的 PPR,建议先停留在当前版本。
选择渲染策略
决策树
需要用户特定数据?
├── 是 -> SSR 或流式渲染
└── 否 -> 数据多久更新一次?
├── 实时 -> SSR
├── 定期 -> ISR
└── 几乎不变 -> SSG
实用建议
- 默认用静态:除非有理由,否则尽量静态渲染
- 按需加动态:只在需要的地方用
dynamic或流式 - ISR 是折中方案:内容多但更新不频繁的场景
- CSR 留给后台:不需要 SEO 的管理界面
平台兼容性
不是所有部署平台都支持 Next.js 的全部特性:
| 部署方式 | 流式 | ISR | PPR |
|---|---|---|---|
| Vercel | ✅ | ✅ | ✅ |
| Node.js 服务器 | ✅ | ✅ | ✅ |
| Docker | ✅ | ✅ | ✅ |
| 静态导出 | ❌ | ❌ | ❌ |
自托管时,ISR 需要额外配置共享缓存才能在多实例间同步。
渲染策略没有银弹。搞清楚每种方式的取舍,按实际场景选就行。