05. 渲染模式 - SSR、SSG、ISR、CSR
概述
Next.js 支持多种渲染模式,每种模式有不同的特点和适用场景。选择合适的渲染模式可以显著提升应用性能、用户体验和 SEO。
| 维度 | 内容 |
|---|---|
| What | Next.js 支持的四种渲染模式 |
| Why | 根据场景选择最佳渲染方式,平衡性能和体验 |
| When | 不同页面类型使用不同渲染模式 |
| Where | 页面/路由级别配置 |
| Who | 需要优化页面加载的开发者 |
| How | SSR:export const dynamic = 'force-dynamic';SSG:export const dynamic = 'force-static';ISR:export const revalidate = 3600 |
1. 渲染模式概览
1.1 四种模式对比
| 模式 | 渲染时机 | HTML 生成 | 缓存 | SEO | 适用场景 |
|---|---|---|---|---|---|
| SSR | 每次请求 | 实时 | 无 | ✅ 好 | 个性化内容 |
| SSG | 构建时 | 静态 | 永久 | ✅ 最好 | 博客、文档 |
| ISR | 构建时 + 定时 | 静态 + 更新 | 时间缓存 | ✅ 好 | 商品列表、新闻 |
| CSR | 客户端 | 动态 | 无 | ⚠️ 差 | 管理后台 |
1.2 渲染流程对比
SSR (Server-Side Rendering) 请求 → 服务器渲染 → HTML 响应 → 客户端水合 SSG (Static Site Generation) 构建 → 生成 HTML → 部署 → 直接响应 ISR (Incremental Static Regeneration) 构建 → 生成 HTML → 部署 → 请求 → 响应缓存 → 后台重新生成 CSR (Client-Side Rendering) 请求 → 空 HTML → 下载 JS → 渲染内容2. SSR (Server-Side Rendering)
2.1 什么是 SSR
SSR 在每个请求到达时在服务器上动态生成 HTML。
// app/products/page.js // SSR: 每次请求都重新渲染 export const dynamic = 'force-dynamic'; async function ProductsPage() { // 每次请求都会执行 const products = await fetch('https://api.example.com/products', { cache: 'no-store', // 确保每次都获取最新数据 }).then(res => res.json()); return ( <div> <h1>商品列表</h1> <p>更新时间: {new Date().toLocaleString()}</p> <ProductList products={products} /> </div> ); } export default ProductsPage;2.2 SSR 适用场景
// ✅ 适合 SSR 的场景: // - 个性化内容(用户仪表盘) // - 实时数据(股票价格) // - 需要认证的页面 // - SEO 重要且数据频繁变化 // app/dashboard/page.js export const dynamic = 'force-dynamic'; async function DashboardPage() { const user = await getCurrentUser(); // 需要认证 const stats = await getUserStats(user.id); // 个性化数据 return ( <div> <h1>欢迎回来, {user.name}</h1> <Stats stats={stats} /> </div> ); }2.3 SSR 配置方式
// 方式1:使用 dynamic 配置 export const dynamic = 'force-dynamic'; // 方式2:使用 fetch 的 no-store fetch(url, { cache: 'no-store' }); // 方式3:使用 cookies() 或 headers() 会自动转为动态 export default async function Page() { const cookie = cookies(); // 自动转为动态渲染 const data = await fetch('https://api.example.com/data'); return <div>{data}</div>; }3. SSG (Static Site Generation)
3.1 什么是 SSG
SSG 在构建时生成静态 HTML,部署后直接响应请求。
// app/about/page.js // SSG: 构建时生成静态页面 export const dynamic = 'force-static'; async function AboutPage() { // 构建时执行一次 const companyInfo = await fetch('https://api.example.com/company', { cache: 'force-cache', }).then(res => res.json()); return ( <div> <h1>关于我们</h1> <p>{companyInfo.description}</p> </div> ); } export default AboutPage;3.2 动态 SSG (generateStaticParams)
// app/blog/[slug]/page.js // 生成所有静态路径 export async function generateStaticParams() { const posts = await fetch('https://api.example.com/posts').then(res => res.json()); return posts.map(post => ({ slug: post.slug, })); } // SSG 模式 export const dynamic = 'force-static'; async function BlogPostPage({ params }) { const { slug } = await params; const post = await fetch(`https://api.example.com/posts/${slug}`).then(res => res.json()); return ( <article> <h1>{post.title}</h1> <p>{post.content}</p> </article> ); } export default BlogPostPage;3.3 SSG 适用场景
// ✅ 适合 SSG 的场景: // - 博客文章 // - 文档网站 // - 营销页面 // - 产品目录(数据变化不频繁) // app/docs/[...slug]/page.js export async function generateStaticParams() { const docs = await getAllDocs(); return docs.map(doc => ({ slug: doc.path.split('/'), })); } export const dynamic = 'force-static';4. ISR (Incremental Static Regeneration)
4.1 什么是 ISR
ISR 结合了 SSG 和 SSR 的优点:构建时生成静态页面,并定时在后台重新生成。
// app/products/page.js // ISR: 每 60 秒重新验证 export const revalidate = 60; // 60 秒后重新生成 async function ProductsPage() { const products = await fetch('https://api.example.com/products', { next: { revalidate: 60 }, }).then(res => res.json()); return ( <div> <h1>商品列表</h1> <p>最后更新: {new Date().toLocaleString()}</p> <ProductList products={products} /> </div> ); } export default ProductsPage;4.2 按需 ISR (On-Demand ISR)
// app/api/revalidate/route.js import { revalidatePath, revalidateTag } from 'next/cache'; import { NextResponse } from 'next/server'; export async function POST(request) { const { path, tag } = await request.json(); // 重新验证特定路径 if (path) { revalidatePath(path); } // 重新验证特定标签 if (tag) { revalidateTag(tag); } return NextResponse.json({ revalidated: true }); }// app/products/page.js async function ProductsPage() { const products = await fetch('https://api.example.com/products', { next: { tags: ['products'] }, // 添加标签 }).then(res => res.json()); return <ProductList products={products} />; } // 当产品更新时,调用 API 触发重新验证 // fetch('/api/revalidate', { method: 'POST', body: JSON.stringify({ tag: 'products' }) })4.3 ISR 配置选项
// app/products/[id]/page.js // 方式1:使用 revalidate 配置 export const revalidate = 3600; // 1 小时 // 方式2:使用 fetch 的 next.revalidate async function ProductPage({ params }) { const product = await fetch(`https://api.example.com/products/${params.id}`, { next: { revalidate: 3600 }, }).then(res => res.json()); return <ProductDetail product={product} />; } // 方式3:结合 generateStaticParams export async function generateStaticParams() { const products = await fetch('https://api.example.com/products').then(res => res.json()); return products.map(product => ({ id: product.id.toString(), })); } export const revalidate = 3600; // 预先构建的页面也会重新验证5. CSR (Client-Side Rendering)
5.1 什么是 CSR
CSR 在客户端渲染内容,服务器只返回空的 HTML 骨架。
// app/dashboard/page.js 'use client'; import { useState, useEffect } from 'react'; // 客户端渲染 export const dynamic = 'force-dynamic'; export default function DashboardPage() { const [data, setData] = useState(null); useEffect(() => { fetch('/api/dashboard-data') .then(res => res.json()) .then(setData); }, []); if (!data) return <div>加载中...</div>; return <Dashboard data={data} />; }5.2 CSR 适用场景
// ✅ 适合 CSR 的场景: // - 需要认证的后台管理 // - 高度交互的应用 // - 不需要 SEO 的页面 // - 受保护的页面 // app/admin/page.js 'use client'; import { useSession } from 'next-auth/react'; import { useRouter } from 'next/navigation'; export default function AdminPage() { const { data: session, status } = useSession(); const router = useRouter(); if (status === 'loading') return <div>加载中...</div>; if (!session) { router.push('/login'); return null; } return <AdminDashboard />; }6. 混合渲染模式
6.1 页面级混合
// app/blog/page.js - SSG // 博客列表页使用 SSG export const dynamic = 'force-static'; async function BlogPage() { const posts = await getPosts(); return <PostList posts={posts} />; } // app/blog/[slug]/page.js - ISR // 博客详情页使用 ISR export const revalidate = 3600; async function BlogPostPage({ params }) { const post = await getPost(params.slug); return <PostDetail post={post} />; } // app/user/profile/page.js - SSR // 用户资料页使用 SSR export const dynamic = 'force-dynamic'; async function ProfilePage() { const user = await getCurrentUser(); return <UserProfile user={user} />; }6.2 组件级混合
// app/products/page.js import { Suspense } from 'react'; async function ProductList() { // 静态数据 - SSG const categories = await fetch('https://api.example.com/categories', { cache: 'force-cache', }).then(res => res.json()); return <CategoryList categories={categories} />; } async function LiveStats() { // 动态数据 - SSR const stats = await fetch('https://api.example.com/live-stats', { cache: 'no-store', }).then(res => res.json()); return <Stats stats={stats} />; } // 页面使用 SSG,但内部组件动态加载 export const dynamic = 'force-static'; export default function ProductsPage() { return ( <div> <Suspense fallback={<div>加载分类...</div>}> <ProductList /> </Suspense> <Suspense fallback={<div>加载实时数据...</div>}> <LiveStats /> </Suspense> </div> ); }7. 渲染模式选择指南
7.1 决策树
需要 SEO? │ ├─ 是 → 数据变化频率? │ │ │ ├─ 从不变化 → SSG │ ├─ 偶尔变化 → ISR │ └─ 频繁变化 → SSR │ └─ 否 → 是否需要快速首屏? │ ├─ 是 → 用户认证? │ │ │ ├─ 是 → SSR │ └─ 否 → SSG/ISR │ └─ 否 → CSR7.2 场景推荐
| 场景 | 推荐模式 | 理由 |
|---|---|---|
| 博客文章 | SSG | 内容不变,SEO 重要 |
| 产品列表 | ISR | 价格库存可能变化 |
| 用户仪表盘 | SSR | 个性化数据,需要认证 |
| 管理后台 | CSR | 不需要 SEO,交互多 |
| 帮助文档 | SSG | 内容静态,SEO 重要 |
| 新闻网站 | ISR | 内容定时更新 |
| 电商首页 | ISR | 促销信息定时更新 |
| 实时看板 | SSR | 需要实时数据 |
8. 完整示例:电商网站
// app/layout.js import { Inter } from 'next/font/google'; import './globals.css'; const inter = Inter({ subsets: ['latin'] }); export const metadata = { title: '我的商店', description: '最好的购物体验', }; export default function RootLayout({ children }) { return ( <html lang="zh-CN"> <body className={inter.className}> <Header /> {children} <Footer /> </body> </html> ); } // app/page.js - 首页 (SSG + ISR 部分) import { Suspense } from 'react'; import HeroBanner from './components/HeroBanner'; import ProductGrid from './components/ProductGrid'; import FlashSale from './components/FlashSale'; // 静态部分 - SSG async function Categories() { const categories = await fetch('https://api.example.com/categories', { cache: 'force-cache', }).then(res => res.json()); return <CategoryList categories={categories} />; } // 准实时部分 - ISR async function RecommendedProducts() { const products = await fetch('https://api.example.com/products/recommended', { next: { revalidate: 3600, tags: ['recommended'] }, }).then(res => res.json()); return <ProductGrid products={products} title="推荐商品" />; } // 动态部分 - SSR async function FlashSaleProducts() { const products = await fetch('https://api.example.com/flash-sale', { cache: 'no-store', }).then(res => res.json()); return <FlashSale products={products} />; } // 主页 export const dynamic = 'force-static'; export default function HomePage() { return ( <main> <HeroBanner /> <Suspense fallback={<div>加载分类...</div>}> <Categories /> </Suspense> <Suspense fallback={<div>加载推荐...</div>}> <RecommendedProducts /> </Suspense> <Suspense fallback={<div>加载限时秒杀...</div>}> <FlashSaleProducts /> </Suspense> </main> ); } // app/products/[id]/page.js - 商品详情 (ISR) export async function generateStaticParams() { // 预先生成热门商品的静态页面 const hotProducts = await fetch('https://api.example.com/products/hot').then(res => res.json()); return hotProducts.map(product => ({ id: product.id.toString(), })); } // ISR: 60 秒后重新验证 export const revalidate = 60; async function ProductPage({ params }) { const { id } = await params; const product = await fetch(`https://api.example.com/products/${id}`, { next: { revalidate: 60, tags: [`product-${id}`] }, }).then(res => res.json()); if (!product) notFound(); return ( <div className="product-detail"> <ProductImages images={product.images} /> <ProductInfo product={product} /> <ProductReviews productId={id} /> </div> ); } // app/cart/page.js - 购物车 (SSR) export const dynamic = 'force-dynamic'; async function CartPage() { // 需要认证和实时数据 const cart = await getCurrentUserCart(); return ( <div> <h1>购物车</h1> <CartItems items={cart.items} /> <CartSummary total={cart.total} /> </div> ); } // app/admin/page.js - 管理后台 (CSR) 'use client'; import { useSession } from 'next-auth/react'; import { useRouter } from 'next/navigation'; export default function AdminPage() { const { data: session, status } = useSession(); const router = useRouter(); if (status === 'loading') return <div>加载中...</div>; if (!session) { router.push('/login'); return null; } return <AdminDashboard />; }9. 总结
核心要点
| 模式 | 配置 | 特点 | 适用场景 |
|---|---|---|---|
| SSR | dynamic: 'force-dynamic' | 实时、个性化 | 用户仪表盘 |
| SSG | dynamic: 'force-static' | 极快、SEO 好 | 博客、文档 |
| ISR | revalidate: n | 平衡、更新 | 产品列表 |
| CSR | 'use client' | 交互多 | 管理后台 |
记忆口诀
SSG 最快 SEO 好,内容不变用它棒
ISR 中间带更新,准实时内容强
SSR 实时个性化,用户数据动态强
CSR 交互最丰富,后台管理很在行
10. 相关资源
- Next.js 渲染模式
- ISR 文档
- generateStaticParams