Next.js的渲染策略决定了页面在服务端、构建时和客户端的处理方式,直接影响首屏加载速度、SEO表现和用户体验。SSR(服务端渲染)、SSG(静态站点生成)和ISR(增量静态再生成)各有适用场景,选错渲染模式会导致性能浪费或功能受限。本文基于Next.js App Router(14+版本),拆解三种渲染模式的实现方式与选择依据。
三种渲染模式的工作机制
SSR在每次请求时由服务端执行React组件渲染,生成完整HTML返回给浏览器。优点是内容实时性和SEO友好,缺点是每次请求都消耗服务端CPU,响应时间取决于数据获取速度。SSG在构建时将页面预渲染为静态HTML文件,运行时由CDN直接返回,无需服务端计算。优点是速度极快、可无限水平扩展,缺点是内容更新需要重新构建。ISR介于两者之间,静态页面在后台按指定间隔自动重新生成,用户请求时返回缓存的旧版本同时触发后台更新。
Next.js App Router默认采用SSG——所有没有动态数据获取的页面在构建时静态生成。只有当组件中使用了动态函数(如cookies()、headers()、searchParams)或显式设置dynamic = ‘force-dynamic’时,才会切换为SSR。这种行为与Pages Router的默认行为不同,迁移时需要注意。
SSG静态生成配置与数据预取
SSG适用于内容相对固定的页面:博客文章、产品详情、文档站点。在App Router中,页面组件默认就是静态的,只需在构建时获取数据:
// app/products/page.tsx
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 } // ISR: 每小时重新生成
})
return res.json()
}
export default async function ProductsPage() {
const products = await getProducts()
return (
<main>
<h1>产品列表</h1>
<ul>
{products.map(product => (
<li key={product.id}>
<Link href={`/products/${product.slug}`}>
{product.name}
</Link>
</li>
))}
</ul>
</main>
)
}
// 生成静态参数(动态路由的SSG)
export async function generateStaticParams() {
const products = await getProducts()
return products.map(product => ({
slug: product.slug
}))
}
next: { revalidate: 3600 }将页面从纯SSG升级为ISR,每隔3600秒后台重新生成。第一次请求返回缓存的旧版本,同时触发后台重新构建,构建完成后后续请求获得新版本。这种stale-while-revalidate策略在内容时效性和性能之间取得平衡。
SSR服务端渲染与动态数据
SSR适用于需要实时数据且无法缓存的页面:用户仪表盘、搜索结果、个性化推荐。在App Router中,使用动态API会自动切换为SSR:
// app/dashboard/page.tsx
import { cookies, headers } from 'next/headers'
export default async function DashboardPage() {
const cookieStore = cookies()
const token = cookieStore.get('auth-token')?.value
if (!token) {
redirect('/login')
}
const userData = await fetch('https://api.example.com/user/profile', {
headers: { Authorization: `Bearer ${token}` }
}).then(res => res.json())
return (
<main>
<h1>{userData.name}的仪表盘</h1>
<UserStats data={userData.stats} />
</main>
)
}
// 显式声明动态渲染
export const dynamic = 'force-dynamic'
export const revalidate = 0
// 基于查询参数的动态渲染
export default async function Page({ searchParams }: {
searchParams: { [key: string]: string | string[] | undefined }
}) {
const query = searchParams.q as string
const results = await searchProducts(query)
return <SearchResults items={results} />
}
SSR页面的数据获取顺序从上到下串行执行。多个独立的数据请求应使用Promise.all并行获取以缩短响应时间:
export default async function Page({ params }: { params: { id: string } }) {
const [product, reviews, related] = await Promise.all([
getProduct(params.id),
getReviews(params.id),
getRelatedProducts(params.id)
])
return (
<>
<ProductDetail product={product} />
<ReviewList reviews={reviews} />
<RelatedProducts products={related} />
</>
)
}
Streaming与Suspense配合
Next.js支持React的Streaming SSR,将页面拆分为多个流式块,先发送已完成渲染的部分,未完成的部分用Suspense占位,数据就绪后再流式追加。这显著改善了首屏时间(TTFB)和Largest Contentful Paint指标。
import { Suspense } from 'react'
export default function DashboardPage() {
return (
<main>
<h1>数据分析仪表盘</h1>
<DashboardHeader />
<Suspense fallback={<SkeletonLoader />}>
<ChartWidget />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<DataTable />
</Suspense>
</main>
)
}
async function ChartWidget() {
const data = await getChartData()
return <Chart data={data} />
}
Streaming模式下,DashboardHeader立即渲染并发送给客户端,ChartWidget和DataTable各自独立获取数据,先就绪的先发送。用户看到的是渐进式加载,而非等待所有数据获取完毕后的空白页面。
渲染模式选择决策矩阵
内容类型 渲染模式 原因
─────────────────────────────────────────────────
营销首页/落地页 SSG 内容固定,需SEO,CDN分发
博客/文档 SSG/ISR 更新频率低,构建时预渲染
电商产品列表 ISR 定期更新,缓存优先
电商产品详情 ISR 价格库存需更新但可接受延迟
用户仪表盘 SSR 实时个性化数据,不缓存
搜索结果 SSR 依赖查询参数,无缓存
实时聊天/协作 CSR 纯客户端渲染,WebSocket驱动
管理后台 CSR 无SEO需求,交互密集
实际项目中,同一应用的不同路由可以使用不同渲染模式。例如电商网站首页用SSG,产品页用ISR,用户中心用SSR,购物车用CSR。Next.js的文件路由系统天然支持这种混合渲染策略。
性能监控与Core Web Vitals优化
'use client'
import { useReportWebVitals } from 'next/web-vitals'
export function WebVitals() {
useReportWebVitals(metric => {
switch (metric.name) {
case 'LCP':
if (metric.value > 2500) {
reportToAnalytics('lcp_slow', metric)
}
break
case 'CLS':
if (metric.value > 0.1) {
reportToAnalytics('cls_high', metric)
}
break
case 'INP':
if (metric.value > 200) {
reportToAnalytics('inp_slow', metric)
}
break
}
})
return null
}
LCP(Largest Contentful Paint)受渲染模式影响最大——SSG和ISR配合CDN可以将LCP控制在1秒以内,而SSR的LCP取决于服务端数据获取速度。对于SSR页面,通过Streaming和Suspense将首屏内容提前到数据获取之前,是改善LCP的有效手段。CLS(Cumulative Layout Shift)则需要确保图片和字体加载时预留空间,避免布局偏移。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/nextjs-quan-zhan-xuan-ran-mo-shi-ssrssgisr-xuan-ran-ce-lyue/