如何实现增量静态再生成(ISR)
增量静态再生成(ISR)使您能够:
- 在不重建整个站点的情况下更新静态内容
- 通过为大多数请求提供预渲染的静态页面来减少服务器负载
- 确保自动为页面添加适当的
cache-control头部 - 处理大量内容页面而无需长时间的
next build时间
以下是一个最小示例:
- TypeScript
- JavaScript
app/blog/[id]/page.tsx
interface Post {
id: string
title: string
content: string
}
// Next.js 将在请求到达时使缓存失效,
// 最多每 60 秒一次。
export const revalidate = 60
// 我们将在构建时仅预渲染来自 `generateStaticParams` 的参数。
// 如果请求的路径尚未生成,
// Next.js 将按需服务端渲染该页面。
export const dynamicParams = true // 或 false,对未知路径返回 404
export async function generateStaticParams() {
const posts: Post[] = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
return posts.map((post) => ({
id: String(post.id),
}))
}
export default async function Page({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
const post: Post = await fetch(`https://api.vercel.app/blog/${id}`).then(
(res) => res.json()
)
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
app/blog/[id]/page.js
// Next.js 将在请求到达时使缓存失效,
// 最多每 60 秒一次。
export const revalidate = 60
// 我们将在构建时仅预渲染来自 `generateStaticParams` 的参数。
// 如果请求的路径尚未生成,
// Next.js 将按需服务端渲染该页面。
export const dynamicParams = true // 或 false,对未知路径返回 404
export async function generateStaticParams() {
const posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
return posts.map((post) => ({
id: String(post.id),
}))
}
export default async function Page({ params }) {
const { id } = await params
const post = await fetch(`https://api.vercel.app/blog/${id}`).then((res) =>
res.json()
)
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
这个示例的工作原理如下:
- 在
next build期间,所有已知的博客文章都会被生成(此示例中有 25 篇) - 对这些页面的所有请求(例如
/blog/1)都会被缓存并立即响应 - 60 秒后,下一个请求仍会显示缓存的(过期的)页面
- 缓存失效,新版本的页面开始在后台生成
- 生成成功后,Next.js 将显示并缓存更新后的页面
- 如果请求
/blog/26,Next.js 将按需生成并缓存此页面
参考
路由段配置
函数
示例
基于时间的重新验证
这会在 /blog 上获取并显示博客文章列表。一小时后,此页面的缓存在下次访问时失效。然后,在后台生成包含最新博客文章的新版本页面。
- TypeScript
- JavaScript
app/blog/page.tsx
interface Post {
id: string
title: string
content: string
}
export const revalidate = 3600 // 每小时失效一次
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts: Post[] = await data.json()
return (
<main>
<h1>博客文章</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
)
}
app/blog/page.js
export const revalidate = 3600 // 每小时失效一次
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return (
<main>
<h1>博客文章</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
)
}
我们建议设置较高的重新验证时间。例如,使用 1 小时而不是 1 秒。如果您需要更精确的控制,请考虑使用按需重新验证。如果您需要实时数据,请考虑切换到动态渲染。
使用 revalidatePath 进行按需重新验证
对于更精确的重新验证方法,使用 revalidatePath 函数按需使页面失效。
例如,这个服务器操作会在添加新文章后被调用。无论您在服务器组件中如何检索数据,无论是使用 fetch 还是连接到数据库,这都会清除整个路由的缓存,并允许服务器组件获取新数据。
- TypeScript
- JavaScript
app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
// 使缓存中的 /posts 路由失效
revalidatePath('/posts')
}
app/actions.js
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
// 使缓存中的 /posts 路由失效
revalidatePath('/posts')
}
使用 revalidateTag 进行按需重新验证
对于大多数用例,建议重新验证整个路径。如果您需要更精细的控制,可以使用 revalidateTag 函数。例如,您可以为单个 fetch 调用添加标签:
- TypeScript
- JavaScript
app/blog/page.tsx
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog', {
next: { tags: ['posts'] },
})
const posts = await data.json()
// ...
}
app/blog/page.js
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog', {
next: { tags: ['posts'] },
})
const posts = await data.json()
// ...
}
如果您使用 ORM 或连接到数据库,可以使用 unstable_cache:
- TypeScript
- JavaScript
app/blog/page.tsx
import { unstable_cache } from 'next/cache'
import { db, posts } from '@/lib/db'
const getCachedPosts = unstable_cache(
async () => {
return await db.select().from(posts)
},
['posts'],
{ revalidate: 3600, tags: ['posts'] }
)
export default async function Page() {
const posts = getCachedPosts()
// ...
}
app/blog/page.js
import { unstable_cache } from 'next/cache'
import { db, posts } from '@/lib/db'
const getCachedPosts = unstable_cache(
async () => {
return await db.select().from(posts)
},
['posts'],
{ revalidate: 3600, tags: ['posts'] }
)
export default async function Page() {
const posts = getCachedPosts()
// ...
}
然后您可以在服务器操作或路由处理器中使用 revalidateTag:
- TypeScript
- JavaScript
app/actions.ts
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
// 使缓存中标记为 'posts' 的所有数据失效
revalidateTag('posts')
}
app/actions.js
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
// 使缓存中标记为 'posts' 的所有数据失效
revalidateTag('posts')
}
处理未捕获的异常
如果在尝试重新验证数据时抛出错误,将继续从缓存中提供最后成功生成的数据。在下一个后续请求中,Next.js 将重试重新验证数据。了解更多关于错误处理的信息。