很多团队在引入 Next.js 后,第一个困惑就是:SSR、SSG、ISR 到底有什么区别?我该用哪种?本文从渲染策略的底层差异讲起,结合 App Router 的实际用法,分享一线性能优化实践。
一、三种渲染策略的本质区别
| 策略 | 渲染时机 | 数据新鲜度 | 适合场景 |
|---|---|---|---|
| SSR(服务端渲染) | 每次请求时 | 实时 | 强个性化、动态数据 |
| SSG(静态生成) | 构建时 | 构建时快照 | 内容型页面、营销页 |
| ISR(增量静态再生) | 构建时 + 后台定时 | 按 revalidate 间隔刷新 | 博客、商品页等半动态内容 |
简单记忆:SSG 是"提前做好",SSR 是"现做现卖",ISR 是"提前做好 + 定期重做"。它们的核心权衡点都是同一个:首屏速度 vs 数据新鲜度。
二、App Router 中的实践
Next.js 13+ 的 App Router 中,通过函数命名声明渲染策略,而不是配置文件,这大大降低了心智负担。
2.1 静态页面(默认)
// app/about/page.tsx
// 不带任何动态 API 时,默认就是静态生成
export default function About() {
return <h1>关于我们</h1>;
}
2.2 动态渲染(SSR)
// app/dashboard/page.tsx
// 读取 cookies / headers / searchParams 时自动变为动态渲染
export default async function Dashboard() {
const user = await getUser();
return <Profile user={user} />;
}
2.3 ISR
// app/posts/[id]/page.tsx
// 每 60 秒后台重新生成一次
export const revalidate = 60;
export default async function Post({ params }: { params: { id: string } }) {
const post = await fetch(`https://api.example.com/posts/${params.id}`,
{ next: { revalidate: 60 } });
return <Article data={await post.json()} />;
}
还有更细粒度的 on-demand revalidation:通过调用 revalidatePath() 或 revalidateTag(),在数据变更(如 CMS 发布文章)时精确失效对应缓存,实现"静态的性能 + 实时的内容"。
三、性能优化实践清单
3.1 图片优化:Image 组件
不要使用原生 <img>,务必使用 next/image,它自动提供:
- WebP/AVIF 格式转换与多尺寸响应式裁切;
- 懒加载(viewport 外图片自动 lazy);
- CLS 消除:显式设置
width/height或使用fill,避免布局偏移; - CDN 缓存与渐进式加载。
import Image from "next/image";
<Image
src="/banner.png"
alt="banner"
width={1200}
height={400}
priority // 首屏关键图设置 priority,立即加载
sizes="(max-width: 768px) 100vw, 50vw"
/>
3.2 字体优化:next/font
next/font 会自动内联字体文件、开启字体子集化,避免布局偏移(FOIT),也避免额外的字体请求:
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
export default function RootLayout({ children }) {
return <html lang="zh-CN" className={inter.className}>{children}</html>;
}
3.3 流式渲染与 Suspense
把慢速数据源包进 <Suspense>,页面会先返回骨架屏,数据就绪后流式填充,大幅降低 TTFB 感知:
export default function Page() {
return (
<div>
<Header /> {/* 立即渲染 */}
<Suspense fallback={<Skeleton />}>
<SlowList /> {/* 数据就绪后流式注入 */}
</Suspense>
</div>
);
}
3.4 缓存与请求合并
- 利用 Next.js 的请求记忆(Request Memoization):同一渲染周期内相同的
fetch请求自动去重; - 开启 CDN 缓存:对 ISR 页面设置合适的
Cache-Control; - 服务端组件中尽量直接查询数据库(如用 Prisma/Drizzle),省去一层 HTTP 开销。
四、常见陷阱
| 陷阱 | 原因 | 解决方案 |
|---|---|---|
| "use client" 滥用 | 整页转客户端渲染,丢失 SSG 优势 | 尽量让叶子组件客户端化,保持页面服务端 |
| 在客户端组件里 fetch | 重复请求、无法使用服务端缓存 | 数据获取移到服务端组件,通过 props 下传 |
| 忽略 revalidate 策略 | ISR 页面内容长期陈旧 | 配合 on-demand revalidation 精确刷新 |
| 图片未指定尺寸 | CLS 分数飙升 | 统一使用 next/image 并声明宽高 |
五、总结
Next.js 的渲染能力是"同一套代码,按页选择策略":营销页用 SSG 拿到满分性能分,个人中心用 SSR 保证数据实时,内容页用 ISR 兼顾两者,再叠加图片、字体、流式渲染三板斧,一个高分的应用就这么搭出来了。
记住核心原则:默认静态,按需动态,缓存为王。