Next.js 中的缓存
Next.js 通过缓存渲染工作和数据请求来提高应用程序的性能并降低成本。此页面深入探讨 Next.js 缓存机制、可用于配置它们的 API 以及它们如何相互作用。
须知:此页面帮助你了解 Next.js 的底层工作原理,但并非高效使用 Next.js 的必要知识。Next.js 的大多数缓存启发式方法由你的 API 使用情况决定,并具有默认值,以便在零配置或最少配置的情况下获得最佳性能。如果你想直接跳转到示例,请从这里开始。
概述
以下是不同缓存机制及其用途的概览
机制 | 内容 | 位置 | 用途 | 持续时间 |
---|---|---|---|---|
请求记忆化 | 函数的返回值 | 服务器 | 在 React 组件树中重用数据 | 每个请求的生命周期 |
数据缓存 | 数据 | 服务器 | 跨用户请求和部署存储数据 | 持久化(可以重新验证) |
完整路由缓存 | HTML 和 RSC 有效负载 | 服务器 | 降低渲染成本并提高性能 | 持久化(可以重新验证) |
路由缓存 | RSC 有效负载 | 客户端 | 减少导航时的服务器请求 | 用户会话或基于时间 |
默认情况下,Next.js 将尽可能多地缓存以提高性能并降低成本。这意味着路由是静态渲染的,数据请求是缓存的,除非你选择退出。下图显示了默认缓存行为:当路由在构建时静态渲染以及静态路由首次被访问时。


缓存行为根据路由是静态渲染还是动态渲染、数据是缓存还是未缓存,以及请求是初始访问还是后续导航的一部分而变化。根据你的用例,你可以为单个路由和数据请求配置缓存行为。
请求记忆化
Next.js 扩展了 fetch
API 以自动记忆化具有相同 URL 和选项的请求。这意味着你可以在 React 组件树的多个位置调用 fetch 函数来获取相同的数据,而只需执行一次。


例如,如果你需要在路由中使用相同的数据(例如,在布局、页面和多个组件中),则不必在树的顶部获取数据,并在组件之间转发 props。相反,你可以在需要数据的组件中获取数据,而无需担心为相同数据跨网络发出多个请求的性能影响。
async function getItem() {
// The `fetch` function is automatically memoized and the result
// is cached
const res = await fetch('https://.../item/1')
return res.json()
}
// This function is called twice, but only executed the first time
const item = await getItem() // cache MISS
// The second call could be anywhere in your route
const item = await getItem() // cache HIT
请求记忆化如何工作


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


- 首次在渲染期间调用带有
'force-cache'
选项的fetch
请求时,Next.js 会检查数据缓存中是否有缓存的响应。 - 如果找到缓存的响应,它会立即返回并被 memoized。
- 如果未找到缓存的响应,则会向数据源发出请求,结果存储在数据缓存中,并被 memoized。
- 对于未缓存的数据(例如,未定义
cache
选项或使用{ cache: 'no-store' }
),结果始终从数据源获取,并被 memoized。 - 无论数据是否被缓存,请求始终会被 memoized,以避免在 React 渲染过程中为相同数据发出重复请求。
数据缓存和请求 Memoization 之间的区别
虽然这两种缓存机制都有助于通过重用缓存数据来提高性能,但数据缓存是跨传入请求和部署持久存在的,而 memoization 仅持续请求的生命周期。
时长
数据缓存跨传入请求和部署持久存在,除非您重新验证或选择退出。
重新验证
缓存的数据可以通过两种方式重新验证:
- 基于时间的重新验证:在经过一定时间后且发出新请求时重新验证数据。这对于不经常更改且对新鲜度要求不高的数据很有用。
- 按需重新验证: 基于事件(例如,表单提交)重新验证数据。按需重新验证可以使用基于标签或基于路径的方法一次性重新验证数据组。当您想确保尽快显示最新数据时(例如,当您的无头 CMS 中的内容更新时),这非常有用。
基于时间的重新验证
要按定时间隔重新验证数据,您可以使用 fetch
的 next.revalidate
选项来设置资源的缓存生命周期(以秒为单位)。
// Revalidate at most every hour
fetch('https://...', { next: { revalidate: 3600 } })
或者,您可以使用路由段配置选项来配置段中所有 fetch
请求,或者在您无法使用 fetch
的情况下使用。
基于时间的重新验证的工作原理


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


- 首次调用
fetch
请求时,数据将从外部数据源获取并存储在数据缓存中。 - 当触发按需重新验证时,相应的缓存条目将从缓存中清除。
- 这与基于时间的重新验证不同,后者会将陈旧数据保留在缓存中,直到获取到新鲜数据。
- 下次发出请求时,它将再次是缓存
MISS
,并且数据将从外部数据源获取并存储在数据缓存中。
选择退出
如果您不想缓存来自 fetch
的响应,您可以执行以下操作
let data = await fetch('https://api.vercel.app/blog', { cache: 'no-store' })
完整路由缓存
相关术语:
您可能会看到术语自动静态优化、静态站点生成或静态渲染被互换使用,以指代在构建时渲染和缓存应用程序路由的过程。
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 服务组件 Payload 存储在客户端的 路由缓存 中 - 一个独立的内存缓存,按各个路由段划分。此路由缓存用于通过存储先前访问过的路由和预取未来路由来改善导航体验。
5. 后续导航
在后续导航或预取期间,Next.js 将检查 React 服务组件 Payload 是否存储在路由缓存中。如果是,它将跳过向服务器发送新请求。
如果路由段不在缓存中,Next.js 将从服务器获取 React 服务组件 Payload,并在客户端填充路由缓存。
静态渲染和动态渲染
路由是否在构建时被缓存取决于它是静态渲染还是动态渲染。静态路由默认情况下会被缓存,而动态路由在请求时渲染,并且不被缓存。
此图表显示了静态渲染路由和动态渲染路由之间的区别,以及缓存和未缓存的数据


了解更多关于 静态和动态渲染 的信息。
时长
默认情况下,完整路由缓存是持久的。这意味着渲染输出在用户请求之间会被缓存。
失效
您可以通过两种方式使完整路由缓存失效
选择退出
您可以通过以下方式选择退出完整路由缓存,或者换句话说,为每个传入的请求动态渲染组件:
- 使用 动态 API:这将使路由选择退出完整路由缓存,并在请求时动态渲染它。数据缓存仍然可以使用。
- 使用
dynamic = 'force-dynamic'
或revalidate = 0
路由段配置选项:这将跳过完整路由缓存和数据缓存。这意味着组件将在每次传入服务器的请求时被渲染和数据被获取。路由缓存仍然适用,因为它是一个客户端缓存。 - 选择退出 数据缓存:如果路由具有未缓存的
fetch
请求,这将使路由选择退出完整路由缓存。对于特定fetch
请求的数据将在每次传入的请求时获取。其他未选择退出缓存的fetch
请求仍将在数据缓存中缓存。这允许缓存和未缓存数据的混合使用。
客户端路由缓存
Next.js 具有一个内存中的客户端路由缓存,用于存储路由段的 RSC payload,按布局、加载状态和页面划分。
当用户在路由之间导航时,Next.js 会缓存访问过的路由段,并预取用户可能导航到的路由。这实现了即时后退/前进导航,导航之间没有完整页面重新加载,并保留 React 状态和浏览器状态。
使用路由缓存
- 布局 在导航时被缓存和重用(部分渲染)。
- 加载状态 在导航时被缓存和重用,以实现即时导航。
- 页面 默认情况下不缓存,但在浏览器后退和前进导航期间会被重用。您可以使用实验性的
staleTimes
配置选项为页面段启用缓存。
须知: 此缓存专门应用于 Next.js 和服务组件,并且与浏览器的 bfcache 不同,尽管它们具有相似的结果。
时长
缓存存储在浏览器的临时内存中。有两个因素决定了路由缓存的持续时间
- 会话:缓存跨导航持久存在。但是,它会在页面刷新时清除。
- 自动失效期:布局和加载状态的缓存会在特定时间后自动失效。持续时间取决于资源的预取方式,以及资源是否是静态生成的
- 默认预取 (
prefetch={null}
或未指定):动态页面不缓存,静态页面缓存 5 分钟。 - 完整预取 (
prefetch={true}
或router.prefetch
):静态和动态页面都缓存 5 分钟。
- 默认预取 (
虽然页面刷新将清除所有缓存的段,但自动失效期仅影响自预取时起的各个段。
须知:实验性的
staleTimes
配置选项可用于调整上述自动失效时间。
失效
您可以通过两种方式使路由缓存失效
- 在 服务端 Action 中
- 使用 (
revalidatePath
) 按路径或使用 (revalidateTag
) 按缓存标签按需重新验证数据 - 使用
cookies.set
或cookies.delete
会使路由缓存失效,以防止使用 cookie 的路由变得陈旧(例如,身份验证)。
- 使用 (
- 调用
router.refresh
将使路由缓存失效,并为当前路由向服务器发出新请求。
选择退出
截至 Next.js 15,页面段默认选择退出缓存。
须知: 您还可以通过将
<Link>
组件的prefetch
属性设置为false
来选择退出预取。
缓存交互
在配置不同的缓存机制时,重要的是要了解它们如何相互作用
数据缓存和完整路由缓存
- 重新验证或选择退出数据缓存将使完整路由缓存失效,因为渲染输出取决于数据。
- 使完整路由缓存失效或选择退出完整路由缓存不会影响数据缓存。您可以动态渲染具有缓存和未缓存数据的路由。当您的大部分页面使用缓存数据,但您有一些组件依赖于需要在请求时获取的数据时,这非常有用。您可以动态渲染,而无需担心重新获取所有数据对性能的影响。
数据缓存和客户端路由缓存
- 要立即使数据缓存和路由缓存失效,您可以在服务端 Action 中使用
revalidatePath
或revalidateTag
。 - 在 路由处理器 中重新验证数据缓存不会立即使路由缓存失效,因为路由处理器不与特定路由绑定。这意味着路由缓存将继续提供之前的 payload,直到硬刷新或自动失效期过去。
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 | 重新验证 (服务端 Action) | 重新验证 | 重新验证 | |
revalidatePath | 重新验证 (服务端 Action) | 重新验证 | 重新验证 | |
const revalidate | 重新验证或选择退出 | 重新验证或选择退出 | ||
const dynamic | 缓存或选择退出 | 缓存或选择退出 | ||
cookies | 重新验证 (服务端 Action) | 选择退出 | ||
headers , searchParams | 选择退出 | |||
generateStaticParams | 缓存 | |||
React.cache | 缓存 | |||
unstable_cache | 缓存 |
<Link>
默认情况下,<Link>
组件自动从完整路由缓存预取路由,并将 React 服务组件 Payload 添加到路由缓存。
要禁用预取,您可以将 prefetch
属性设置为 false
。但这不会永久跳过缓存,当用户访问路由时,路由段仍将在客户端缓存。
了解更多关于 <Link>
组件 的信息。
router.prefetch
useRouter
Hook 的 prefetch
选项可用于手动预取路由。这会将 React 服务组件 Payload 添加到路由缓存。
请参阅 useRouter
Hook API 参考。
router.refresh
useRouter
Hook 的 refresh
选项可用于手动刷新路由。这将完全清除路由缓存,并为当前路由向服务器发出新请求。refresh
不影响数据缓存或完整路由缓存。
渲染结果将在客户端进行协调,同时保留 React 状态和浏览器状态。
请参阅 useRouter
Hook API 参考。
fetch
从 fetch
返回的数据不会自动缓存在数据缓存中。
fetch
的默认缓存行为(例如,当未指定 cache
选项时)等同于将 cache
选项设置为 no-store
let data = await fetch('https://api.vercel.app/blog', { cache: 'no-store' })
请参阅 fetch
API 参考 以获取更多选项。
fetch options.cache
你可以通过将 cache
选项设置为 force-cache
,选择性地为单个 fetch
请求启用缓存。
// Opt into caching
fetch(`https://...`, { cache: 'force-cache' })
请参阅 fetch
API 参考 以获取更多选项。
fetch options.next.revalidate
你可以使用 fetch
的 next.revalidate
选项来设置单个 fetch
请求的重新验证周期(以秒为单位)。这将重新验证数据缓存,进而重新验证完整路由缓存。将获取新的数据,并在服务器上重新渲染组件。
// Revalidate at most after 1 hour
fetch(`https://...`, { next: { revalidate: 3600 } })
请参阅 fetch
API 参考 以了解更多选项。
fetch options.next.tags
和 revalidateTag
Next.js 具有缓存标签系统,用于细粒度的数据缓存和重新验证。
- 当使用
fetch
或unstable_cache
时,你可以选择使用一个或多个标签标记缓存条目。 - 然后,你可以调用
revalidateTag
来清除与该标签关联的缓存条目。
例如,你可以在获取数据时设置一个标签
// Cache data with a tag
fetch(`https://...`, { next: { tags: ['a', 'b', 'c'] } })
然后,使用标签调用 revalidateTag
以清除缓存条目
// Revalidate entries with a specific tag
revalidateTag('a')
根据你想要实现的目标,你可以使用 revalidateTag
在两个地方:
- 路由处理器(Route Handlers) - 用于响应第三方事件(例如,webhook)重新验证数据。这不会立即使路由器缓存失效,因为路由器处理器未与特定路由关联。
- 服务器操作(Server Actions) - 用于在用户操作(例如,表单提交)后重新验证数据。这将使关联路由的路由器缓存失效。
revalidatePath
revalidatePath
允许你手动重新验证数据和在单个操作中重新渲染特定路径下的路由段。调用 revalidatePath
方法会重新验证数据缓存,进而使完整路由缓存失效。
revalidatePath('/')
根据你想要实现的目标,你可以使用 revalidatePath
在两个地方:
- 路由处理器(Route Handlers) - 用于响应第三方事件(例如,webhook)重新验证数据。
- 服务器操作(Server Actions) - 用于在用户交互(例如,表单提交、点击按钮)后重新验证数据。
请参阅 revalidatePath
API 参考 以获取更多信息。
revalidatePath
与router.refresh
调用
router.refresh
将清除路由器缓存,并在服务器上重新渲染路由段,而不会使数据缓存或完整路由缓存失效。区别在于
revalidatePath
会清除数据缓存和完整路由缓存,而router.refresh()
不会更改数据缓存和完整路由缓存,因为它是一个客户端 API。
动态 API
动态 API,如 cookies
和 headers
,以及 Pages 中的 searchParams
属性,都依赖于运行时的传入请求信息。使用它们会将路由选择退出完整路由缓存;换句话说,该路由将动态渲染。
cookies
在服务器操作中使用 cookies.set
或 cookies.delete
会使路由器缓存失效,以防止使用 cookies 的路由变得陈旧(例如,反映身份验证更改)。
请参阅 cookies
API 参考。
分段配置选项
路由分段配置选项可用于覆盖路由分段默认值,或者当你无法使用 fetch
API 时(例如,数据库客户端或第三方库)。
以下路由分段配置选项将选择退出完整路由缓存:
const dynamic = 'force-dynamic'
此配置选项将使所有 fetch 请求选择退出数据缓存(即 no-store
)。
const fetchCache = 'default-no-store'
请参阅 fetchCache
以查看更多高级选项。
请参阅 路由分段配置 文档以了解更多选项。
generateStaticParams
对于 动态分段(例如,app/blog/[slug]/page.js
),generateStaticParams
提供的路径在构建时会被缓存到完整路由缓存中。在请求时,Next.js 也会缓存构建时未知的路径,在它们首次被访问时。
要在构建时静态渲染所有路径,请向 generateStaticParams
提供完整的路径列表
export async function generateStaticParams() {
const posts = await fetch('https://.../posts').then((res) => res.json())
return posts.map((post) => ({
slug: post.slug,
}))
}
要在构建时静态渲染路径的子集,并在运行时首次访问时渲染其余路径,请返回路径的部分列表
export async function generateStaticParams() {
const posts = await fetch('https://.../posts').then((res) => res.json())
// Render the first 10 posts at build time
return posts.slice(0, 10).map((post) => ({
slug: post.slug,
}))
}
要静态渲染所有路径,在它们首次被访问时,请返回一个空数组(构建时不会渲染任何路径)或使用 export const dynamic = 'force-static'
export async function generateStaticParams() {
return []
}
须知: 你必须从
generateStaticParams
返回一个数组,即使它是空的。否则,路由将被动态渲染。
export const dynamic = 'force-static'
要在请求时禁用缓存,请在路由分段中添加 export const dynamicParams = false
选项。当使用此配置选项时,只会提供 generateStaticParams
提供的路径,其他路由将返回 404 或匹配(在 catch-all 路由 的情况下)。
React cache
函数
React cache
函数允许你记忆化函数的返回值,允许你多次调用同一个函数,但只执行一次。
由于 fetch
请求会自动记忆化,因此你无需将其包装在 React cache
中。但是,当 fetch
API 不适用时,你可以使用 cache
手动记忆化数据请求以用于用例。例如,某些数据库客户端、CMS 客户端或 GraphQL 客户端。
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
})
这有帮助吗?