增量静态再生 (ISR)
Examples
增量静态再生 (ISR) 使你能够:
¥Incremental Static Regeneration (ISR) enables you to:
-
无需重建整个站点即可更新静态内容
¥Update static content without rebuilding the entire site
-
通过为大多数请求提供预渲染的静态页面来减少服务器负载
¥Reduce server load by serving prerendered, static pages for most requests
-
确保将正确的
cache-control
标头自动添加到页面¥Ensure proper
cache-control
headers are automatically added to pages -
无需长时间
next build
即可处理大量内容页面¥Handle large amounts of content pages without long
next build
times
这是一个最小的例子:
¥Here's a minimal example:
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
// We'll prerender only the params from `generateStaticParams` at build time.
// If a request comes in for a path that hasn't been generated,
// Next.js will server-render the page on-demand.
export const dynamicParams = true // or false, to 404 on unknown paths
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: { id: string } }) {
const post: Post = await fetch(
`https://api.vercel.app/blog/${params.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
// We'll prerender only the params from `generateStaticParams` at build time.
// If a request comes in for a path that hasn't been generated,
// Next.js will server-render the page on-demand.
export const dynamicParams = true // or false, to 404 on unknown paths
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 post = await fetch(`https://api.vercel.app/blog/${params.id}`).then(
(res) => res.json()
)
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
此示例的工作原理如下:
¥Here's how this example works:
-
在
next build
期间,将生成所有已知博客文章(此示例中有 25 篇)¥During
next build
, all known blog posts are generated (there are 25 in this example) -
对这些页面(例如
/blog/1
)的所有请求都已缓存并即时执行¥All requests made to these pages (e.g.
/blog/1
) are cached and instantaneous -
60 秒后,下一个请求仍将显示缓存(过时)页面
¥After 60 seconds has passed, the next request will still show the cached (stale) page
-
缓存失效,新版本的页面开始在后台生成
¥The cache is invalidated and a new version of the page begins generating in the background
-
成功生成后,Next.js 将显示并缓存更新的页面
¥Once generated successfully, Next.js will display and cache the updated page
-
如果请求
/blog/26
,Next.js 将按需生成并缓存此页面¥If
/blog/26
is requested, Next.js will generate and cache this page on-demand
参考
¥Reference
路由段配置
¥Route segment config
函数
¥Functions
示例
¥Examples
基于时间的重新验证
¥Time-based revalidation
这将提取并显示 /blog
上的博客文章列表。一小时后,下次访问该页面时,此页面的缓存将失效。然后,在后台,将使用最新的博客文章生成页面的新版本。
¥This fetches and displays a list of blog posts on /blog
. After an hour, the cache for this page is invalidated on the next visit to the page. Then, in the background, a new version of the page is generated with the latest blog posts.
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 秒。如果你需要更高的精度,请考虑使用按需重新验证。如果你需要实时数据,请考虑切换到 动态渲染。
¥We recommend setting a high revalidation time. For instance, 1 hour instead of 1 second. If you need more precision, consider using on-demand revalidation. If you need real-time data, consider switching to dynamic rendering.
使用 revalidatePath
进行按需重新验证
¥On-demand revalidation with revalidatePath
对于更精确的重新验证方法,使用 revalidatePath
函数按需使页面无效。
¥For a more precise method of revalidation, invalidate pages on-demand with the revalidatePath
function.
例如,添加新帖子后将调用此服务器操作。无论你如何在服务器组件中检索数据,无论是使用 fetch
还是连接到数据库,这都会清除整个路由的缓存并允许服务器组件获取新数据。
¥For example, this Server Action would get called after adding a new post. Regardless of how you retrieve your data in your Server Component, either using fetch
or connecting to a database, this will clear the cache for the entire route and allow the Server Component to fetch fresh data.
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
// Invalidate the /posts route in the cache
revalidatePath('/posts')
}
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
// Invalidate the /posts route in the cache
revalidatePath('/posts')
}
¥View a demo and explore the source code.
使用 revalidateTag
进行按需重新验证
¥On-demand revalidation with revalidateTag
对于大多数用例,最好重新验证整个路径。如果你需要更精细的控制,你可以使用 revalidateTag
函数。例如,你可以标记单个 fetch
调用:
¥For most use cases, prefer revalidating entire paths. If you need more granular control, you can use the revalidateTag
function. For example, you can tag individual fetch
calls:
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
:
¥If you are using an ORM or connecting to a database, you can use 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()
// ...
}
然后,你可以在 服务器操作 或 路由处理程序 中使用 revalidateTag
:
¥You can then use revalidateTag
in a Server Actions or Route Handler:
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
// Invalidate all data tagged with 'posts' in the cache
revalidateTag('posts')
}
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
// Invalidate all data tagged with 'posts' in the cache
revalidateTag('posts')
}
处理未捕获的异常
¥Handling uncaught exceptions
如果在尝试重新验证数据时抛出错误,则将继续从缓存中提供最后成功生成的数据。在下一个后续请求中,Next.js 将重试重新验证数据。了解有关错误处理的更多信息。
¥If an error is thrown while attempting to revalidate data, the last successfully generated data will continue to be served from the cache. On the next subsequent request, Next.js will retry revalidating the data. Learn more about error handling.
自定义缓存位置
¥Customizing the cache location
缓存和重新验证页面(使用增量静态再生)使用相同的共享缓存。当 部署到 Vercel 时,ISR 缓存会自动保存到持久存储中。
¥Caching and revalidating pages (with Incremental Static Regeneration) use the same shared cache. When deploying to Vercel, the ISR cache is automatically persisted to durable storage.
当自托管时,ISR 缓存将存储到 Next.js 服务器上的文件系统(磁盘上)。当使用 Pages 和 App Router 进行自托管时,此功能会自动起作用。
¥When self-hosting, the ISR cache is stored to the filesystem (on disk) on your Next.js server. This works automatically when self-hosting using both the Pages and App Router.
如果你想要将缓存的页面和数据持久保存到持久存储,或者在 Next.js 应用的多个容器或实例之间共享缓存,你可以配置 Next.js 缓存位置。了解更多。
¥You can configure the Next.js cache location if you want to persist cached pages and data to durable storage, or share the cache across multiple containers or instances of your Next.js application. Learn more.
故障排除
¥Troubleshooting
在本地开发中调试缓存数据
¥Debugging cached data in local development
如果你使用 fetch
API,则可以添加其他日志记录以了解哪些请求已缓存或未缓存。了解有关 logging
选项的更多信息。
¥If you are using the fetch
API, you can add additional logging to understand which requests are cached or uncached. Learn more about the logging
option.
module.exports = {
logging: {
fetches: {
fullUrl: true,
},
},
}
验证正确的生产行为
¥Verifying correct production behavior
要验证你的页面在生产中是否已正确缓存和重新验证,你可以通过运行 next build
然后运行 next start
来运行生产 Next.js 服务器,在本地进行测试。
¥To verify your pages are cached and revalidated correctly in production, you can test locally by running next build
and then next start
to run the production Next.js server.
这将允许你测试 ISR 行为,就像它在生产环境中一样。为了进一步调试,请将以下环境变量添加到你的 .env
文件中:
¥This will allow you to test ISR behavior as it would work in a production environment. For further debugging, add the following environment variable to your .env
file:
NEXT_PRIVATE_DEBUG_CACHE=1
这将使 Next.js 服务器控制台记录 ISR 缓存命中和未命中。你可以检查输出以查看在 next build
期间生成了哪些页面,以及在按需访问路径时如何更新页面。
¥This will make the Next.js server console log ISR cache hits and misses. You can inspect the output to see which pages are generated during next build
, as well as how pages are updated as paths are accessed on-demand.
注意事项
¥Caveats
-
仅在使用 Node.js 运行时(默认)时才支持 ISR。
¥ISR is only supported when using the Node.js runtime (default).
-
创建 静态导出 时不支持 ISR。
¥ISR is not supported when creating a Static Export.
-
如果你在静态渲染的路由中有多个
fetch
请求,并且每个请求都有不同的revalidate
频率,则将使用最短的时间进行 ISR。但是,数据缓存 仍将尊重这些重新验证频率。¥If you have multiple
fetch
requests in a statically rendered route, and each has a differentrevalidate
frequency, the lowest time will be used for ISR. However, those revalidate frequencies will still be respected by the Data Cache. -
如果在路由上使用的任何
fetch
请求的revalidate
时间为0
,或显式no-store
,则路由将为 动态渲染。¥If any of the
fetch
requests used on a route have arevalidate
time of0
, or an explicitno-store
, the route will be dynamically rendered. -
中间件不会针对按需 ISR 请求执行,这意味着中间件中的任何路径重写或逻辑都不会应用。确保你正在重新验证确切的路径。例如,
/post/1
而不是重写的/post-1
。¥Middleware won't be executed for on-demand ISR requests, meaning any path rewrites or logic in Middleware will not be applied. Ensure you are revalidating the exact path. For example,
/post/1
instead of a rewritten/post-1
.
版本历史记录
¥Version history
版本 | 变化 |
---|---|
v14.1.0 | 定制 cacheHandler 稳定。 |
v13.0.0 | 引入应用路由。 |
v12.2.0 | Pages Router:按需 ISR 稳定 |
v12.0.0 | Pages Router:添加了 机器人感知 ISR 后备。 |
v9.5.0 | Pages Router:稳定的 ISR 介绍。 |