Next.js 中的缓存机制
Next.js 通过缓存渲染工作和数据请求来提升应用性能并降低成本。本文将深入探讨 Next.js 的缓存机制、可用的配置 API 以及它们之间的交互方式。
须知:本文帮助您理解 Next.js 的底层工作原理,但并非高效使用 Next.js 的必备知识。Next.js 的大部分缓存启发式规则由您的 API 使用情况决定,并提供了零配置或最小配置即可获得最佳性能的默认设置。
概览
以下是不同缓存机制及其用途的高级概览:
机制 | 缓存内容 | 位置 | 目的 | 持续时间 |
---|---|---|---|---|
请求记忆化 | 函数返回值 | 服务端 | 在 React 组件树中复用数据 | 单次请求生命周期 |
数据缓存 | 数据 | 服务端 | 跨用户请求和部署存储数据 | 持久化(可重新验证) |
全路由缓存 | HTML 和 RSC 负载 | 服务端 | 降低渲染成本并提升性能 | 持久化(可重新验证) |
路由缓存 | RSC 负载 | 客户端 | 减少导航时的服务端请求 | 用户会话或基于时间 |
默认情况下,Next.js 会尽可能多地缓存以提升性能并降低成本。这意味着路由会静态渲染且数据请求会被缓存,除非您主动选择退出。下图展示了默认缓存行为:当路由在构建时静态渲染以及首次访问静态路由时的情况。

缓存行为会根据路由是静态还是动态渲染、数据是否缓存以及请求是首次访问还是后续导航而变化。根据您的使用场景,可以为单个路由和数据请求配置缓存行为。
请求记忆化
React 扩展了 fetch
API 以自动记忆化具有相同 URL 和选项的请求。这意味着您可以在 React 组件树的多个位置调用相同的 fetch 函数,而实际上只会执行一次。

例如,如果您需要在路由中跨组件使用相同数据(如在布局、页面和多个组件中),您不必在组件树顶部获取数据并通过 props 向下传递。相反,您可以在需要数据的组件中直接获取,而无需担心因多次网络请求相同数据而影响性能。
async function getItem() {
// `fetch` 函数会自动记忆化,结果会被缓存
const res = await fetch('https://.../item/1')
return res.json()
}
// 此函数被调用两次,但仅第一次会执行
const item = await getItem() // cache MISS
// 第二次调用可以发生在路由的任何位置
const item = await getItem() // cache HIT
async function getItem() {
// `fetch` 函数会自动记忆化,结果会被缓存
const res = await fetch('https://.../item/1')
return res.json()
}
// 此函数被调用两次,但仅第一次会执行
const item = await getItem() // cache MISS
// 第二次调用可以发生在路由的任何位置
const item = await getItem() // cache HIT
请求记忆化工作原理

- 在渲染路由时,首次调用特定请求时,其结果不在内存中,因此是缓存
MISS
。 - 函数将被执行,从外部源获取数据,结果存储在内存中。
- 同一渲染过程中对该请求的后续调用将是缓存
HIT
,数据直接从内存返回而无需执行函数。 - 路由渲染完成后,内存会被"重置",所有请求记忆化条目被清除。
须知:
- 请求记忆化是 React 特性,而非 Next.js 特性。此处提及是为了展示它与其他缓存机制的交互。
- 记忆化仅适用于
fetch
请求的GET
方法。- 记忆化仅适用于 React 组件树,这意味着:
- 它适用于
generateMetadata
、generateStaticParams
、布局、页面和其他服务端组件中的fetch
请求。- 不适用于路由处理器中的
fetch
请求,因为它们不属于 React 组件树。- 对于不适用
fetch
的情况(如某些数据库客户端、CMS 客户端或 GraphQL 客户端),可以使用 Reactcache
函数 来记忆化函数。
持续时间
缓存持续到服务端请求生命周期结束,即 React 组件树完成渲染时。
重新验证
由于记忆化不跨服务端请求共享且仅在渲染期间有效,因此无需重新验证。
选择退出
要在 fetch
请求中退出记忆化,可以向请求传递 AbortController
的 signal
。
const { signal } = new AbortController()
fetch(url, { signal })
数据缓存
Next.js 内置了数据缓存,可在服务端请求和部署间持久化数据获取结果。这是通过扩展原生 fetch
API 实现的,允许每个服务端请求设置自己的持久化缓存语义。
须知:在浏览器中,
fetch
的cache
选项表示请求如何与浏览器的 HTTP 缓存交互;在 Next.js 中,cache
选项表示服务端请求如何与服务器的数据缓存交互。
默认情况下,使用 fetch
的数据请求会被缓存。您可以使用 fetch
的 cache
和 next.revalidate
选项来配置缓存行为。
数据缓存工作原理

- 渲染期间首次调用
fetch
请求时,Next.js 会检查数据缓存中是否有缓存响应。 - 如果找到缓存响应,则立即返回并记忆化。
- 如果未找到缓存响应,则向数据源发起请求,结果存储在数据缓存中并被记忆化。
- 对于非缓存数据(如
{ cache: 'no-store' }
),总是从数据源获取结果并被记忆化。 - 无论数据是否缓存,请求都会被记忆化以避免在 React 渲染过程中对相同数据发起重复请求。
数据缓存与请求记忆化的区别
虽然两种缓存机制都通过重用缓存数据提升性能,但数据缓存在请求和部署间持久化,而记忆化仅持续单次请求的生命周期。
通过记忆化,我们减少了同一渲染过程中需要跨越网络边界(从渲染服务器到数据缓存服务器,如 CDN 或边缘网络)或数据源(如数据库或 CMS)的重复请求数量。通过数据缓存,我们减少了对原始数据源的请求数量。
持续时间
除非重新验证或选择退出,否则数据缓存在请求和部署间持久化。
重新验证
缓存数据可通过两种方式重新验证:
- 基于时间的重新验证:在一定时间间隔后重新验证数据。适用于变化不频繁且时效性要求不高的数据。
- 按需重新验证:基于事件(如表单提交)重新验证数据。可按标签或路径一次性重新验证一组数据。适用于需要尽快展示最新数据的场景(如无头 CMS 内容更新时)。
基于时间的重新验证
要按时间间隔重新验证数据,可使用 fetch
的 next.revalidate
选项设置资源的缓存生命周期(秒)。
// 最多每小时重新验证一次
fetch('https://...', { next: { revalidate: 3600 } })
或者,可以使用路由段配置选项来配置段内所有 fetch
请求,或在不使用 fetch
的情况下配置。
基于时间的重新验证工作原理

- 首次调用带
revalidate
的fetch
请求时,数据从外部数据源获取并存储在数据缓存中。 - 在指定时间范围内(如 60 秒)的任何请求都将返回缓存数据。
- 时间范围过后,下一次请求仍返回缓存(现已过期)数据。
- Next.js 将在后台触发数据重新验证。
- 成功获取数据后,Next.js 会用新数据更新数据缓存。
- 如果后台重新验证失败,则保留原数据不变。
这与 stale-while-revalidate 行为类似。
按需重新验证
可通过路径 (revalidatePath
) 或缓存标签 (revalidateTag
) 按需重新验证数据。
按需重新验证工作原理

- 首次调用
fetch
请求时,数据从外部数据源获取并存储在数据缓存中。 - 触发按需重新验证时,相应的缓存条目将从缓存中清除。
- 这与基于时间的重新验证不同,后者会在获取新数据前保留过期数据。
- 下次请求时,将再次出现缓存
MISS
,数据将从外部数据源获取并存储在数据缓存中。
选择退出
对于单个数据获取,可通过将 cache
选项设为 no-store
来退出缓存。这意味着每次调用 fetch
都会获取数据。
// 为单个 `fetch` 请求退出缓存
fetch(`https://...`, { cache: 'no-store' })
或者,也可以使用路由段配置选项为特定路由段退出缓存。这将影响段内所有数据请求,包括第三方库。
// 为路由段内所有数据请求退出缓存
export const dynamic = 'force-dynamic'
Vercel 数据缓存
如果您的 Next.js 应用部署在 Vercel 上,建议阅读 Vercel 数据缓存 文档以了解 Vercel 特有功能。
全路由缓存
相关术语:
您可能会看到自动静态优化、静态站点生成或静态渲染等术语互换使用,均指在构建时渲染和缓存应用路由的过程。
Next.js 会在构建时自动渲染和缓存路由。这项优化允许您直接提供缓存路由,而无需为每个请求在服务端渲染,从而实现更快的页面加载。
要理解全路由缓存的工作原理,了解 React 如何处理渲染以及 Next.js 如何缓存结果很有帮助:
1. 服务端的 React 渲染
在服务端,Next.js 使用 React 的 API 编排渲染。渲染工作被拆分为多个块:按单个路由段和 Suspense 边界划分。
每个块的渲染分为两步:
- React 将服务端组件渲染为一种专为流式传输优化的特殊数据格式,称为 React 服务端组件负载。
- Next.js 使用 React 服务端组件负载和客户端组件 JavaScript 指令在服务端渲染 HTML。
这意味着我们不必等待所有内容渲染完成即可缓存工作或发送响应。相反,我们可以在工作完成时流式传输响应。
什么是 React 服务端组件负载?
React 服务端组件负载是渲染后的 React 服务端组件树的紧凑二进制表示。React 在客户端使用它来更新浏览器的 DOM。React 服务端组件负载包含:
- 服务端组件的渲染结果
- 客户端组件应渲染位置的占位符及其 JavaScript 文件的引用
- 从服务端组件传递给客户端组件的任何 props
了解更多,请参阅服务端组件文档。
2. Next.js 服务端缓存(全路由缓存)

Next.js 的默认行为是在服务端缓存路由的渲染结果(React 服务端组件负载和 HTML)。这适用于构建时静态渲染的路由或在重新验证期间的路由。
3. 客户端的 React 水合与协调
在请求时,客户端会:
- 使用 HTML 快速展示客户端和服务端组件的非交互式初始预览。
- 使用 React 服务端组件负载协调客户端和已渲染的服务端组件树,并更新 DOM。
- 使用 JavaScript 指令水合客户端组件,使应用可交互。
4. Next.js 客户端缓存(路由缓存)
React 服务端组件负载存储在客户端路由缓存中——这是一个按单个路由段分割的独立内存缓存。路由缓存通过存储先前访问的路由和预取未来路由来提升导航体验。
5. 后续导航
在后续导航或预取过程中,Next.js 会检查路由缓存 (Router Cache) 中是否已存储 React 服务端组件负载 (React Server Components Payload)。若存在,则会跳过向服务器发送新请求。
如果路由段未在缓存中,Next.js 将从服务器获取 React 服务端组件负载,并在客户端填充路由缓存。
静态与动态渲染
路由是否在构建时被缓存取决于其采用静态渲染 (Static Rendering) 还是动态渲染 (Dynamic Rendering)。静态路由默认会被缓存,而动态路由则在请求时渲染且不被缓存。
下图展示了静态与动态渲染路由的区别,以及缓存与未缓存数据的差异:

了解更多关于静态与动态渲染的内容。
持续时间
默认情况下,全路由缓存是持久化的。这意味着渲染输出会在用户请求间持续缓存。
失效机制
有两种方式可以使全路由缓存失效:
- 数据重新验证 (Revalidating Data):重新验证数据缓存 (Data Cache) 会触发服务端组件重新渲染,并缓存新的渲染输出,从而使路由缓存失效。
- 重新部署:与跨部署持久化的数据缓存不同,全路由缓存会在新部署时被清除。
退出机制
您可以通过以下方式退出全路由缓存(即针对每个传入请求动态渲染组件):
- 使用动态函数 (Dynamic Function):这将使路由退出全路由缓存,并在请求时动态渲染。数据缓存仍可被使用。
- 使用路由段配置选项
dynamic = 'force-dynamic'
或revalidate = 0
:这会跳过全路由缓存和数据缓存。意味着每次传入请求时都会在服务端渲染组件并获取数据。路由缓存仍会生效,因为它是客户端缓存。 - 退出数据缓存 (Data Cache):如果路由包含未被缓存的
fetch
请求,该路由将退出全路由缓存。针对该特定fetch
请求的数据会在每次传入请求时获取。其他未退出缓存的fetch
请求仍会被保存在数据缓存中。这允许混合使用缓存与非缓存数据。
路由缓存
相关术语:
路由缓存 (Router Cache) 可能被称为客户端缓存 (Client-side Cache) 或预取缓存 (Prefetch Cache)。其中预取缓存特指预取的路由段,而客户端缓存指整个路由缓存,包含已访问和预取的路由段。 此缓存专用于 Next.js 和服务端组件,与浏览器的 bfcache 不同,尽管效果相似。
Next.js 拥有一个内存中的客户端缓存,用于在用户会话期间存储按路由段分割的 React 服务端组件负载。这被称为路由缓存。
路由缓存工作原理

当用户在路由间导航时,Next.js 会缓存已访问的路由段,并预取用户可能导航到的路由(基于视窗中的 <Link>
组件)。
这会为用户带来更优的导航体验:
- 即时前进/后退导航,因为已访问路由被缓存;快速导航到新路由,得益于预取和部分渲染 (Partial Rendering)。
- 导航间无需整页刷新,且 React 状态与浏览器状态得以保留。
路由缓存与全路由缓存的区别:
路由缓存临时在浏览器中存储 React 服务端组件负载(用户会话期间),而全路由缓存在服务器端持久化存储 React 服务端组件负载和 HTML(跨用户请求)。
全路由缓存仅缓存静态渲染的路由,而路由缓存同时适用于静态和动态渲染的路由。
持续时间
缓存存储在浏览器的临时内存中。两个因素决定路由缓存的持续时间:
- 会话 (Session):缓存在导航间持续存在,但页面刷新时会被清除。
- 自动失效周期 (Automatic Invalidation Period):单个路由段的缓存会在特定时间后自动失效。持续时间取决于路由是静态还是动态渲染:
- 动态渲染:30 秒
- 静态渲染:5 分钟
页面刷新会清除所有缓存的路由段,而自动失效周期仅影响自上次访问或创建后的单个路由段。
通过为动态渲染的路由添加 prefetch={true}
或调用 router.prefetch
,您可以选择将缓存时间延长至 5 分钟。
失效机制
有两种方式可以使路由缓存失效:
- 在服务端操作 (Server Action) 中:
- 按路径 (
revalidatePath
) 或缓存标签 (revalidateTag
) 按需重新验证数据 - 使用
cookies.set
或cookies.delete
会使路由缓存失效,以防止使用 cookie 的路由(如身份验证)过期。
- 按路径 (
- 调用
router.refresh
会使路由缓存失效,并为当前路由向服务器发起新请求。
退出机制
无法完全退出路由缓存。
您可以通过将 <Link>
组件的 prefetch
属性设为 false
来退出预取。但这仍会临时存储路由段 30 秒,以实现嵌套路由段(如标签栏)间的即时导航或前进/后退导航。已访问的路由仍会被缓存。
缓存交互
配置不同缓存机制时,理解它们之间的交互方式至关重要:
数据缓存与全路由缓存
- 重新验证或退出数据缓存会使全路由缓存失效,因为渲染输出依赖于数据。
- 使全路由缓存失效或退出全路由缓存不会影响数据缓存。您可以动态渲染一个同时包含缓存和非缓存数据的路由。这在页面大部分使用缓存数据,但部分组件依赖需实时获取的数据时非常有用。您可以动态渲染,而无需担心重新获取所有数据对性能的影响。
数据缓存与客户端路由缓存
- 在路由处理器 (Route Handler) 中重新验证数据缓存不会立即使路由缓存失效,因为路由处理器不与特定路由绑定。这意味着路由缓存将继续提供之前的负载,直到强制刷新或自动失效周期结束。
- 要立即使数据缓存和路由缓存失效,您可以在服务端操作 (Server Action) 中使用
revalidatePath
或revalidateTag
。
API
下表概述了不同 Next.js API 对缓存的影响:
API | 路由缓存 | 全路由缓存 | 数据缓存 | React 缓存 |
---|---|---|---|---|
<Link prefetch> | 缓存 | |||
router.prefetch | 缓存 | |||
router.refresh | 重新验证 | |||
fetch | 缓存 | 缓存 | ||
fetch options.cache | 缓存或退出 | |||
fetch options.next.revalidate | 重新验证 | 重新验证 | ||
fetch options.next.tags | 缓存 | 缓存 | ||
revalidateTag | 重新验证(服务端操作) | 重新验证 | 重新验证 | |
revalidatePath | 重新验证(服务端操作) | 重新验证 | 重新验证 | |
const revalidate | 重新验证或退出 | 重新验证或退出 | ||
const dynamic | 缓存或退出 | 缓存或退出 | ||
cookies | 重新验证(服务端操作) | 退出 | ||
headers , useSearchParams , searchParams | 退出 | |||
generateStaticParams | 缓存 | |||
React.cache | 缓存 | |||
unstable_cache (即将推出) |
<Link>
默认情况下,<Link>
组件会自动从全路由缓存预取路由,并将 React 服务端组件负载添加到路由缓存。
要禁用预取,可将 prefetch
属性设为 false
。但这不会永久跳过缓存,用户访问路由时仍会在客户端缓存该路由段。
了解更多关于 <Link>
组件的内容。
router.prefetch
useRouter
钩子的 prefetch
选项可用于手动预取路由。这会将 React 服务端组件负载添加到路由缓存。
查看 useRouter
钩子 API 参考。
router.refresh
useRouter
钩子的 refresh
选项可用于手动刷新路由。这会完全清除路由缓存,并为当前路由向服务器发起新请求。refresh
不会影响数据缓存或全路由缓存。
渲染结果将在客户端协调,同时保留 React 状态和浏览器状态。
查看 useRouter
钩子 API 参考。
fetch
通过 fetch
返回的数据会自动缓存在数据缓存中。
// 默认缓存。`force-cache` 是默认选项,可省略。
fetch(`https://...`, { cache: 'force-cache' })
查看 fetch
API 参考 获取更多选项。
fetch options.cache
您可以通过将 cache
选项设为 no-store
来退出单个 fetch
请求的数据缓存:
// 退出缓存
fetch(`https://...`, { cache: 'no-store' })
由于渲染输出依赖于数据,使用 cache: 'no-store'
还会跳过使用该 fetch
请求的路由的全路由缓存。即该路由会在每次请求时动态渲染,但同一路由中仍可包含其他缓存的数据请求。
查看 fetch
API 参考 获取更多选项。
fetch options.next.revalidate
您可以使用 fetch
的 next.revalidate
选项设置单个 fetch
请求的重新验证周期(秒)。这将重新验证数据缓存,进而重新验证全路由缓存。将获取新数据,并在服务端重新渲染组件。
// 最多 1 小时后重新验证
fetch(`https://...`, { next: { revalidate: 3600 } })
查看 fetch
API 参考 获取更多选项。
fetch options.next.tags
与 revalidateTag
Next.js 拥有一个缓存标签系统,用于细粒度的数据缓存和重新验证。
- 使用
fetch
或unstable_cache
时,您可以选择用一个或多个标签标记缓存条目。 - 然后,您可以调用
revalidateTag
清除与该标签关联的缓存条目。
例如,您可以在获取数据时设置标签:
// 使用标签缓存数据
fetch(`https://...`, { next: { tags: ['a', 'b', 'c'] } })
然后,调用 revalidateTag
并传入标签以清除缓存条目:
// 重新验证具有特定标签的条目
revalidateTag('a')
根据您的需求,可以在两个位置使用 revalidateTag
:
- 路由处理器 (Route Handlers) - 在第三方事件(如 webhook)响应中重新验证数据。这不会立即使路由缓存失效,因为路由处理器不与特定路由绑定。
- 服务端操作 (Server Actions) - 在用户操作(如表单提交)后重新验证数据。这将立即使相关路由的路由缓存失效。
revalidatePath
revalidatePath
允许您手动重新验证数据 并 在单次操作中重新渲染特定路径下的路由段。调用 revalidatePath
方法会重新验证数据缓存 (Data Cache),进而使完整路由缓存 (Full Route Cache) 失效。
revalidatePath('/')
根据您的需求,可以在以下两种场景中使用 revalidatePath
:
- 路由处理器 (Route Handlers) —— 用于响应第三方事件(如 Webhook)时重新验证数据。
- 服务器操作 (Server Actions) —— 在用户交互(如表单提交、点击按钮)后重新验证数据。
更多信息请参阅 revalidatePath
API 参考文档。
revalidatePath
与router.refresh
的区别:调用
router.refresh
会清除路由缓存 (Router Cache),并在服务端重新渲染路由段,但不会使数据缓存 (Data Cache) 或完整路由缓存 (Full Route Cache) 失效。两者的区别在于:
revalidatePath
会清空数据缓存和完整路由缓存,而router.refresh()
作为客户端 API 不会改变数据缓存和完整路由缓存的状态。
动态函数 (Dynamic Functions)
cookies
、headers
、useSearchParams
和 searchParams
都是依赖运行时请求信息的动态函数。使用这些函数会使路由退出完整路由缓存 (Full Route Cache),即该路由将转为动态渲染。
cookies
在服务器操作 (Server Action) 中使用 cookies.set
或 cookies.delete
会使路由缓存 (Router Cache) 失效,从而防止使用 cookie 的路由变得过时(例如反映身份验证状态的变化)。
参阅 cookies
API 参考文档。
路由段配置选项 (Segment Config Options)
路由段配置选项可用于覆盖默认设置,或在无法使用 fetch
API 时(如使用数据库客户端或第三方库)进行配置。
以下路由段配置选项会使数据缓存 (Data Cache) 和完整路由缓存 (Full Route Cache) 失效:
const dynamic = 'force-dynamic'
const revalidate = 0
更多选项请参阅 路由段配置文档。
generateStaticParams
对于动态路由 (dynamic segments)(如 app/blog/[slug]/page.js
),由 generateStaticParams
提供的路径会在构建时被缓存到完整路由缓存 (Full Route Cache) 中。在请求时,Next.js 也会在首次访问时缓存那些构建时未知的路径。
您可以通过在路由段中使用 export const dynamicParams = false
选项来禁用请求时的缓存。启用此配置后,只有 generateStaticParams
提供的路径会被响应,其他路由将返回 404 或匹配(对于通配路由 (catch-all routes) 的情况)。
参阅 generateStaticParams
API 参考文档。
React cache
函数
React cache
函数允许您对函数的返回值进行记忆化 (memoize),从而在多次调用同一函数时只执行一次。
由于 fetch
请求会自动记忆化,您无需再用 React cache
进行包装。但在不适合使用 fetch
API 的场景下(如某些数据库客户端、CMS 客户端或 GraphQL 客户端),可以使用 cache
手动记忆化数据请求。
import { cache } from 'react'
import db from '@/lib/db'
export const getItem = cache(async (id: string) => {
const item = await db.item.findUnique({ id })
return item
})
import { cache } from 'react'
import db from '@/lib/db'
export const getItem = cache(async (id) => {
const item = await db.item.findUnique({ id })
return item
})
unstable_cache
unstable_cache
是一个实验性 API,用于在不适合使用 fetch
API 时(如使用数据库客户端、CMS 客户端或 GraphQL)向数据缓存 (Data Cache) 添加值。
import { unstable_cache } from 'next/cache'
export default async function Page() {
const cachedData = await unstable_cache(
async () => {
const data = await db.query('...')
return data
},
['cache-key'],
{
tags: ['a', 'b', 'c'],
revalidate: 10,
}
)()
}
警告:此 API 正在开发中,我们不建议在生产环境中使用。此处列出是为了展示数据缓存的未来发展方向。