增量静态再生成 (Incremental Static Regeneration, ISR) 使您能够:
cache-control 头部next build 时间这是一个最小示例:
interface Post {
id: string
title: string
content: string
}
// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
export const revalidate = 60
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>
)
}// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
export const revalidate = 60
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 期间,所有已知的博客文章都会被生成/blog/1)的所有请求都会被缓存并立即响应/blog/26 且该文章存在,页面将按需生成。此行为可以通过使用不同的 dynamicParams 值来更改。但是,如果文章不存在,则返回 404。import type { GetStaticPaths, GetStaticProps } from 'next'
interface Post {
id: string
title: string
content: string
}
interface Props {
post: Post
}
export const getStaticPaths: GetStaticPaths = async () => {
const posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
const paths = posts.map((post: Post) => ({
params: { id: String(post.id) },
}))
return { paths, fallback: 'blocking' }
}
export const getStaticProps: GetStaticProps<Props> = async ({
params,
}: {
params: { id: string }
}) => {
const post = await fetch(`https://api.vercel.app/blog/${params.id}`).then(
(res) => res.json()
)
return {
props: { post },
// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
revalidate: 60,
}
}
export default function Page({ post }: Props) {
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}export async function getStaticPaths() {
const posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
const paths = posts.map((post) => ({
params: { id: post.id },
}))
return { paths, fallback: 'blocking' }
}
export async function getStaticProps({ params }) {
const post = await fetch(`https://api.vercel.app/blog/${params.id}`).then(
(res) => res.json()
)
return {
props: { post },
// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
revalidate: 60,
}
}
export default function Page({ post }) {
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}此示例的工作原理如下:
next build 期间,所有已知的博客文章都会被生成/blog/1)的所有请求都会被缓存并立即响应/blog/26 且该文章存在,页面将按需生成。此行为可以通过使用不同的 fallback 值来更改。但是,如果文章不存在,则返回 404。此示例获取并显示 /blog 上的博客文章列表。一小时过后,下一个访问者仍会立即收到页面的缓存(过期)版本以获得快速响应。同时,Next.js 会在后台触发新版本的重新生成。一旦新版本成功生成,它将替换缓存版本,后续访问者将收到更新的内容。
interface Post {
id: string
title: string
content: string
}
export const revalidate = 3600 // invalidate every hour
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts: Post[] = await data.json()
return (
<main>
<h1>Blog Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
)
}export const revalidate = 3600 // invalidate every hour
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return (
<main>
<h1>Blog Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
)
}我们建议设置较长的重新验证时间。例如,1 小时而不是 1 秒。如果您需要更高的精度,请考虑使用按需重新验证。如果您需要实时数据,请考虑切换到 动态渲染。
revalidatePath 进行按需重新验证为了更精确的重新验证方法,请使用 revalidatePath 函数按需使缓存页面失效。
例如,此 Server Action 会在添加新文章后调用。无论您在 Server Component 中使用 fetch 还是连接到数据库来检索数据,这都将使整个路由的缓存失效。对该路由的下一次请求将触发重新生成并提供最新数据,然后该数据将缓存以供后续请求使用。
注意:
revalidatePath会使缓存条目失效,但重新生成会在下一次请求时发生。如果您想立即主动重新生成缓存条目,而不是等待下一次请求,可以使用 Pages 路由器中的res.revalidate方法。我们正在努力添加新方法,为 App Router 提供主动重新生成功能。
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
// Invalidate the cache for the /posts route
revalidatePath('/posts')
}'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
// Invalidate the cache for the /posts route
revalidatePath('/posts')
}revalidateTag 进行按需重新验证对于大多数用例,首选重新验证整个路径。如果您需要更细粒度的控制,可以使用 revalidateTag 函数。例如,您可以为单个 fetch 调用添加标签:
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog', {
next: { tags: ['posts'] },
})
const posts = await data.json()
// ...
}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:
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()
// ...
}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()
// ...
}然后您可以在 Server Actions 或 Route Handler 中使用 revalidateTag:
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
// Invalidate all data tagged with 'posts'
revalidateTag('posts')
}'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
// Invalidate all data tagged with 'posts'
revalidateTag('posts')
}res.revalidate() 进行按需验证为了更精确的重新验证方法,请使用 res.revalidate 从 API 路由按需生成新页面。
例如,这个 API 路由可以在 /api/revalidate?secret=<token> 被调用,以重新验证给定的博客文章。创建一个只有您的 Next.js 应用程序知道的秘密令牌。这个秘密令牌将用于防止未经授权访问重新验证 API 路由。
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
// Check for secret to confirm this is a valid request
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: 'Invalid token' })
}
try {
// This should be the actual path not a rewritten path
// e.g. for "/posts/[id]" this should be "/posts/1"
await res.revalidate('/posts/1')
return res.json({ revalidated: true })
} catch (err) {
// If there was an error, Next.js will continue
// to show the last successfully generated page
return res.status(500).send('Error revalidating')
}
}export default async function handler(req, res) {
// Check for secret to confirm this is a valid request
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: 'Invalid token' })
}
try {
// This should be the actual path not a rewritten path
// e.g. for "/posts/[id]" this should be "/posts/1"
await res.revalidate('/posts/1')
return res.json({ revalidated: true })
} catch (err) {
// If there was an error, Next.js will continue
// to show the last successfully generated page
return res.status(500).send('Error revalidating')
}
}如果您正在使用按需重新验证,则无需在 getStaticProps 中指定 revalidate 时间。Next.js 将使用默认值 false(不重新验证),并且仅在调用 res.revalidate() 时按需重新验证页面。
如果尝试重新验证数据时抛出错误,则上次成功生成的数据将继续从缓存中提供。在下一次请求时,Next.js 将重试重新验证数据。了解更多关于错误处理。
如果在处理后台重新生成时 getStaticProps 内部出现错误,或者您手动抛出错误,则上次成功生成的页面将继续显示。在下一次请求时,Next.js 将重试调用 getStaticProps。
import type { GetStaticProps } from 'next'
interface Post {
id: string
title: string
content: string
}
interface Props {
post: Post
}
export const getStaticProps: GetStaticProps<Props> = async ({
params,
}: {
params: { id: string }
}) => {
// If this request throws an uncaught error, Next.js will
// not invalidate the currently shown page and
// retry getStaticProps on the next request.
const res = await fetch(`https://api.vercel.app/blog/${params.id}`)
const post: Post = await res.json()
if (!res.ok) {
// If there is a server error, you might want to
// throw an error instead of returning so that the cache is not updated
// until the next successful request.
throw new Error(`Failed to fetch posts, received status ${res.status}`)
}
return {
props: { post },
// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
revalidate: 60,
}
}export async function getStaticProps({ params }) {
// If this request throws an uncaught error, Next.js will
// not invalidate the currently shown page and
// retry getStaticProps on the next request.
const res = await fetch(`https://api.vercel.app/blog/${params.id}`)
const post = await res.json()
if (!res.ok) {
// If there is a server error, you might want to
// throw an error instead of returning so that the cache is not updated
// until the next successful request.
throw new Error(`Failed to fetch posts, received status ${res.status}`)
}
return {
props: { post },
// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
revalidate: 60,
}
}您可以配置 Next.js 缓存位置,以便将缓存页面和数据持久化到持久存储中,或在 Next.js 应用程序的多个容器或实例之间共享缓存。了解更多。
如果您正在使用 fetch API,可以添加额外的日志记录来了解哪些请求被缓存或未缓存。了解更多关于 logging 选项的信息。
module.exports = {
logging: {
fetches: {
fullUrl: true,
},
},
}要验证您的页面在生产环境中是否正确缓存和重新验证,您可以在本地运行 next build,然后运行 next start 来启动生产 Next.js 服务器进行测试。
这将允许您测试 ISR 行为,就像它在生产环境中工作一样。为了进一步调试,请将以下环境变量添加到您的 .env 文件中:
NEXT_PRIVATE_DEBUG_CACHE=1这将使 Next.js 服务器在控制台日志中输出 ISR 缓存命中和未命中。您可以检查输出,查看哪些页面在 next build 期间生成,以及页面在路径按需访问时如何更新。
/post/1 而不是重写后的 /post-1。| 部署选项 | 支持 |
|---|---|
| Node.js 服务器 | 是 |
| Docker 容器 | 是 |
| 静态导出 | 否 |
| 适配器 | 平台特定 |
了解在自托管 Next.js 时如何 配置 ISR。
| 版本 | 变更 |
|---|---|
v14.1.0 | 自定义 cacheHandler 稳定。 |
v13.0.0 | App Router 推出。 |
v12.2.0 | Pages Router:按需 ISR 稳定。 |
v12.0.0 | Pages Router:添加了 支持机器人的 ISR fallback。 |
v9.5.0 | Pages Router:稳定的 ISR 推出。 |