Menu

Incremental Static Regeneration (ISR)

示例

增量静态再生 (ISR) 使你能够:

  • 无需重新构建整个网站就能更新静态内容
  • 通过对大多数请求提供预渲染的静态页面来减少服务器负载
  • 确保页面自动添加正确的 cache-control headers
  • 在不增加 next build 时间的情况下处理大量内容页面

下面是一个最小示例:

pages/blog/[id].tsx
TypeScript
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) },
  }))
 
  // 我们只会在构建时预渲染这些路径。
  // { fallback: 'blocking' } 将在路径不存在时
  // 按需服务器渲染页面。
  return { paths, fallback: false }
}
 
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 将在请求进来时使缓存失效,
    // 最多每 60 秒一次。
    revalidate: 60,
  }
}
 
export default function Page({ post }: Props) {
  return (
    <main>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </main>
  )
}

这个示例的工作原理如下:

  1. next build 期间,生成所有已知的博客文章 (本示例中有 25 篇)
  2. 对这些页面的所有请求 (例如 /blog/1) 都被缓存并立即响应
  3. 60 秒过后,下一个请求仍然会显示缓存的 (陈旧的) 页面
  4. 缓存失效,并在后台开始生成页面的新版本
  5. 一旦成功生成,Next.js 将显示并缓存更新后的页面
  6. 如果请求 /blog/26,Next.js 将按需生成并缓存这个页面

参考

函数

示例

使用 res.revalidate() 进行按需验证

对于更精确的重新验证方法,可以使用 res.revalidate 从 API 路由按需生成新页面。

例如,这个 API 路由可以通过访问 /api/revalidate?secret=<token> 来重新验证指定的博客文章。创建一个只有你的 Next.js 应用知道的密钥令牌。这个密钥将用于防止未经授权访问重新验证 API 路由。

pages/api/revalidate.ts
import type { NextApiRequest, NextApiResponse } from 'next'
 
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  // 检查密钥以确认这是一个有效请求
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: '无效令牌' })
  }
 
  try {
    // 这应该是实际路径而不是重写后的路径
    // 例如对于 "/posts/[id]" 应该是 "/posts/1"
    await res.revalidate('/posts/1')
    return res.json({ revalidated: true })
  } catch (err) {
    // 如果发生错误,Next.js 将继续
    // 显示最后一个成功生成的页面
    return res.status(500).send('重新验证出错')
  }
}
pages/api/revalidate.js
export default async function handler(req, res) {
  // 检查密钥以确认这是一个有效请求
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: '无效令牌' })
  }
 
  try {
    // 这应该是实际路径而不是重写后的路径
    // 例如对于 "/posts/[id]" 应该是 "/posts/1"
    await res.revalidate('/posts/1')
    return res.json({ revalidated: true })
  } catch (err) {
    // 如果发生错误,Next.js 将继续
    // 显示最后一个成功生成的页面
    return res.status(500).send('重新验证出错')
  }
}

如果你使用按需重新验证,则不需要在 getStaticProps 中指定 revalidate 时间。Next.js 将使用默认值 false (不重新验证),并且仅在调用 res.revalidate() 时按需重新验证页面。

处理未捕获的异常

如果在处理后台重新生成时 getStaticProps 内部发生错误,或者你手动抛出错误,最后一次成功生成的页面将继续显示。在下一次后续请求中,Next.js 将重试调用 getStaticProps

pages/blog/[id].tsx
TypeScript
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 }
}) => {
  // 如果这个请求抛出未捕获的错误,Next.js 将
  // 不会使当前显示的页面失效,并且
  // 会在下一个请求时重试 getStaticProps。
  const res = await fetch(`https://api.vercel.app/blog/${params.id}`)
  const post: Post = await res.json()
 
  if (!res.ok) {
    // 如果出现服务器错误,你可能想要
    // 抛出错误而不是返回,这样缓存就不会更新
    // 直到下一次成功请求。
    throw new Error(`获取文章失败,收到状态码 ${res.status}`)
  }
 
  return {
    props: { post },
    // Next.js 将在请求进来时使缓存失效,
    // 最多每 60 秒一次。
    revalidate: 60,
  }
}

自定义缓存位置

缓存和重新验证页面(使用增量静态再生)使用相同的共享缓存。当部署到 Vercel 时,ISR 缓存会自动持久化到持久存储中。

在自托管时,ISR 缓存存储在 Next.js 服务器的文件系统(磁盘)中。这在使用页面路由器和应用路由器进行自托管时都会自动工作。

如果你想将缓存的页面和数据持久化到持久存储中,或者在 Next.js 应用程序的多个容器或实例之间共享缓存,可以配置 Next.js 缓存位置。了解更多

故障排除

在本地开发中调试缓存数据

如果你使用 fetch API,可以添加额外的日志来了解哪些请求被缓存或未缓存。了解更多关于 logging 选项

next.config.js
module.exports = {
  logging: {
    fetches: {
      fullUrl: true,
    },
  },
}

验证正确的生产行为

要验证你的页面在生产环境中是否正确缓存和重新验证,你可以通过运行 next build 然后 next start 来在本地运行生产版 Next.js 服务器进行测试。

这将允许你测试 ISR 行为,就像在生产环境中一样。要进行进一步的调试,在你的 .env 文件中添加以下环境变量:

.env
NEXT_PRIVATE_DEBUG_CACHE=1

这将使 Next.js 服务器在控制台记录 ISR 缓存命中和未命中。你可以检查输出,查看在 next build 期间生成了哪些页面,以及在按需访问路径时页面是如何更新的。

注意事项

  • ISR 仅在使用 Node.js 运行时(默认)时受支持。
  • ISR 在创建静态导出时不受支持。
  • 中间件不会在按需 ISR 请求时执行,这意味着中间件中的任何路径重写或逻辑都不会被应用。确保你重新验证的是准确的路径。例如,使用 /post/1 而不是重写后的 /post-1

版本历史

版本变更
v14.1.0自定义 cacheHandler 功能稳定。
v13.0.0引入 App Router。
v12.2.0Pages Router: 按需 ISR 功能稳定。
v12.0.0Pages Router: 添加机器人感知 ISR 回退
v9.5.0Pages Router: 引入稳定版 ISR