🙋 页面应该在构建时生成还是请求时生成?如何平衡性能和数据新鲜度?
渲染模式概览#
| 模式 | 全称 | 生成时机 | 适用场景 |
|---|---|---|---|
| SSG | Static Site Generation | 构建时 | 内容不常变化 |
| SSR | Server-Side Rendering | 请求时 | 实时数据 |
| ISR | Incremental Static Regeneration | 构建 + 增量更新 | 折中方案 |
静态渲染(SSG)#
默认行为#
没有动态函数的页面默认静态渲染:
// Next.js 15.x// 这是静态页面,构建时生成
export default function AboutPage() { return ( <main> <h1>关于我们</h1> <p>这是静态内容,构建时生成。</p> </main> )}带数据的静态页面#
// Next.js 15.x
async function getPosts() { // 默认行为:构建时获取,永久缓存 const res = await fetch('https://api.example.com/posts') return res.json()}
export default async function PostsPage() { const posts = await getPosts()
return ( <ul> {posts.map((post: any) => ( <li key={post.id}>{post.title}</li> ))} </ul> )}动态路由静态生成#
使用 generateStaticParams 预生成路径:
// Next.js 15.x
export async function generateStaticParams() { const posts = await fetch('https://api.example.com/posts').then((r) => r.json() )
return posts.map((post: { slug: string }) => ({ slug: post.slug, }))}
export default async function BlogPost({ params,}: { params: Promise<{ slug: string }>}) { const { slug } = await params const post = await fetch(`https://api.example.com/posts/${slug}`).then((r) => r.json() )
return <article>{post.content}</article>}🎯 效果:构建时为每个 slug 生成静态 HTML。
动态渲染(SSR)#
使用动态函数#
以下函数会触发动态渲染:
// Next.js 15.ximport { cookies, headers } from 'next/headers'
export default async function DashboardPage() { // 使用 cookies() 触发动态渲染 const cookieStore = await cookies() const token = cookieStore.get('token')
// 使用 headers() 也会触发 const headersList = await headers() const userAgent = headersList.get('user-agent')
return <div>动态内容</div>}禁用缓存#
// Next.js 15.x
async function getData() { const res = await fetch('https://api.example.com/data', { cache: 'no-store', // 禁用缓存,每次请求都获取新数据 }) return res.json()}
export default async function RealtimePage() { const data = await getData() return <div>{data.value}</div>}路由段配置#
// Next.js 15.x
// 强制动态渲染export const dynamic = 'force-dynamic'
// 或强制静态渲染// export const dynamic = 'force-static'
export default function LivePage() { return <div>实时页面</div>}增量静态再生成(ISR)#
基于时间的重新验证#
// Next.js 15.x
async function getNews() { const res = await fetch('https://api.example.com/news', { next: { revalidate: 60 }, // 60 秒后重新验证 }) return res.json()}
export default async function NewsPage() { const news = await getNews()
return ( <ul> {news.map((item: any) => ( <li key={item.id}>{item.title}</li> ))} </ul> )}🎯 行为:
- 首次请求:返回缓存的 HTML
- 60 秒内的请求:继续返回缓存
- 60 秒后的请求:返回缓存,同时后台重新生成
- 下次请求:返回新生成的 HTML
路由级别配置#
// Next.js 15.x
// 整个路由段的重新验证时间export const revalidate = 3600 // 1 小时
export default async function ProductsPage() { const products = await fetch('https://api.example.com/products').then((r) => r.json() ) return <div>{/* 产品列表 */}</div>}按需重新验证#
// Next.js 15.ximport { revalidatePath, revalidateTag } from 'next/cache'import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) { const { path, tag, secret } = await request.json()
// 验证密钥 if (secret !== process.env.REVALIDATE_SECRET) { return NextResponse.json({ error: 'Invalid secret' }, { status: 401 }) }
// 按路径重新验证 if (path) { revalidatePath(path) }
// 按标签重新验证 if (tag) { revalidateTag(tag) }
return NextResponse.json({ revalidated: true })}// Next.js 15.x
async function getPost(slug: string) { const res = await fetch(`https://api.example.com/posts/${slug}`, { next: { tags: ['posts', `post-${slug}`] }, // 添加缓存标签 }) return res.json()}调用 API 触发重新验证:
curl -X POST http://localhost:3000/api/revalidate \ -H "Content-Type: application/json" \ -d '{"tag": "posts", "secret": "your-secret"}'缓存配置详解#
fetch 缓存选项#
// 默认:永久缓存(SSG)fetch('https://api.example.com/data')
// 禁用缓存(SSR)fetch('https://api.example.com/data', { cache: 'no-store' })
// 定时重新验证(ISR)fetch('https://api.example.com/data', { next: { revalidate: 60 } })
// 缓存标签fetch('https://api.example.com/data', { next: { tags: ['tag1'] } })组合使用#
// Next.js 15.x
async function getUser() { // 用户信息:每次请求都获取 const res = await fetch('https://api.example.com/me', { cache: 'no-store', }) return res.json()}
async function getStats() { // 统计数据:5 分钟缓存 const res = await fetch('https://api.example.com/stats', { next: { revalidate: 300 }, }) return res.json()}
async function getConfig() { // 配置:构建时获取,永久缓存 const res = await fetch('https://api.example.com/config') return res.json()}
export default async function DashboardPage() { const [user, stats, config] = await Promise.all([ getUser(), getStats(), getConfig(), ])
return <div>{/* 使用数据渲染 */}</div>}选择渲染模式#
决策流程#
数据需要实时更新? │ ├─ 是 → 需要个性化内容? │ ├─ 是 → SSR(动态渲染) │ └─ 否 → ISR(定时更新) │ └─ 否 → SSG(静态渲染)场景示例#
| 页面类型 | 推荐模式 | 原因 |
|---|---|---|
| 关于页面 | SSG | 内容固定 |
| 博客文章 | ISR | 偶尔更新 |
| 商品列表 | ISR | 定期同步 |
| 用户仪表盘 | SSR | 个性化数据 |
| 购物车 | SSR | 实时状态 |
| 营销落地页 | SSG | 内容固定 |
实战配置#
电商产品页#
// Next.js 15.x
// 预生成热门产品export async function generateStaticParams() { const topProducts = await fetch( 'https://api.example.com/products/top100' ).then((r) => r.json())
return topProducts.map((p: { id: string }) => ({ id: p.id }))}
// 允许未预生成的路径动态生成export const dynamicParams = true
// 产品信息 1 小时更新async function getProduct(id: string) { const res = await fetch(`https://api.example.com/products/${id}`, { next: { revalidate: 3600, tags: [`product-${id}`] }, }) return res.json()}
// 库存实时获取async function getStock(id: string) { const res = await fetch(`https://api.example.com/products/${id}/stock`, { cache: 'no-store', }) return res.json()}
export default async function ProductPage({ params,}: { params: Promise<{ id: string }>}) { const { id } = await params const [product, stock] = await Promise.all([getProduct(id), getStock(id)])
return ( <div> <h1>{product.name}</h1> <p>{product.description}</p> <p>库存: {stock.quantity}</p> </div> )}新闻网站首页#
// Next.js 15.x
// 头条新闻:5 分钟更新async function getHeadlines() { const res = await fetch('https://api.example.com/headlines', { next: { revalidate: 300, tags: ['headlines'] }, }) return res.json()}
// 热门文章:1 小时更新async function getTrending() { const res = await fetch('https://api.example.com/trending', { next: { revalidate: 3600, tags: ['trending'] }, }) return res.json()}
// 天气:15 分钟更新async function getWeather() { const res = await fetch('https://api.example.com/weather', { next: { revalidate: 900 }, }) return res.json()}
export default async function HomePage() { const [headlines, trending, weather] = await Promise.all([ getHeadlines(), getTrending(), getWeather(), ])
return ( <main> <section> <h2>头条新闻</h2> {/* 渲染头条 */} </section> <section> <h2>热门文章</h2> {/* 渲染热门 */} </section> <aside> <h3>天气</h3> {/* 渲染天气 */} </aside> </main> )}常见问题#
🤔 Q: 如何查看页面是静态还是动态?
构建时查看输出:
○静态页面λ动态页面●ISR 页面
🤔 Q: ISR 的 revalidate 是精确时间吗?
不是。revalidate 是最小时间间隔,实际更新发生在间隔后的第一次请求。
🤔 Q: 如何强制所有 fetch 使用相同缓存策略?
export const fetchCache = 'force-cache' // 或 'force-no-store'恭喜你完成了渲染模式的学习!下一篇将进入数据处理部分,介绍数据获取的最佳实践。
-EOF-