缓存
Next.js 通过缓存渲染工作和数据请求,提高了应用程序的性能并降低了成本。 本页面深入探讨了 Next.js 缓存机制、可用于配置它们的 API 以及它们之间的交互。
本页面帮助你了解 Next.js 在内部是如何工作的,但这不是使用 Next.js 时必需的知识。 大多数 Next.js 的缓存启发法则是通过你的 API 使用决定的, 并且对于最佳性能通常具有零或最小配置的默认值。
概述
以下是不同缓存机制及其目的的高级概述:
机制 | 作用 | 位置 | 目的 | 持续时间 |
---|---|---|---|---|
请求记忆 | 函数返回值 | 服务器 | 在 React 组件树中重用数据 | 每个请求生命周期 |
数据缓存 | 数据 | 服务器 | 在用户请求和部署之间存储数据 | 持久的(可重新验证) |
完整路由缓存 | HTML 和 RSC 负载 | 服务器 | 减少渲染成本,提高性能 | 持久的(可重新验证) |
路由缓存 | RSC 负载 | 客户端 | 在导航时减少服务器请求 | 用户会话或基于时间 |
默认情况下,Next.js 会尽可能多地使用缓存以提高性能并降低成本。 这意味着路由是静态渲染的,数据请求被缓存,除非你选择退出。 下面的图表展示了默认的缓存行为:当在构建时静态渲染路由以及首次访问静态路由时。
缓存行为会根据路由是静态渲染还是动态渲染、数据是否被缓存或未缓存, 以及请求是初始访问还是后续导航而发生变化。 根据您的用例,您可以为单个路由和数据请求配置缓存行为。
请求记忆
React 扩展了 [fetch
API](https://nextjs.org/docs/app/building-your-application/caching#fetch), 自动**记忆**具有相同 URL 和选项的请求。 这意味着你可以在 React 组件树的多个位置调用
fetch` 函数以获取相同的数据,而只执行一次。
例如,如果您需要在路由中使用相同的数据(例如在布局、页面和多个组件中), 则无需在树的顶部获取数据,然后在组件之间转发 props。 相反,您可以在需要的组件中获取数据,而不必担心为相同的数据在网络上进行多次请求的性能影响。
async function getItem() {
// `fetch` 函数会自动被记忆和缓存结果
const res = await fetch('https://.../item/1')
return res.json()
}
// 此函数被调用两次,但只执行了一次
const item = await getItem() // 缓存 MISS
// 第二次调用可以在您的路由的任何位置
const item = await getItem() // 缓存 HIT
请求记忆的工作原理
- 在渲染路由时,第一次调用特定请求时,其结果将不在内存中,因此会是缓存
MISS
。 - 因此,将执行函数,从外部源获取数据,并将结果存储在内存中。
- 在同一渲染通行中,对相同请求的后续函数调用将是缓存
HIT
,并且数据将从内存中返回而无需执行函数。 - 一旦路由已渲染并且渲染通行完成,内存将被“重置”, 所有请求记忆条目将被清除。
值得知道:
- 请求记忆是 React 的一个功能,而不是 Next.js 的一个功能。在这里包含它是为了展示它如何与其他缓存机制交互。
- 记忆仅适用于 fetch 请求中的
GET
方法。 - 记忆仅适用于 React 组件树,这意味着:
- 它适用于
generateMetadata
、generateStaticParams
、Layouts
、Pages
和其他 Server Components 中的fetch
请求。 - 不适用于 Route Handlers 中的
fetch
请求,因为它们不是 React 组件树的一部分。
- 它适用于
- 对于不适合使用
fetch
的情况(例如某些数据库客户端、CMS 客户端或 GraphQL 客户端),您可以使用 React 缓存函数来记忆函数。
持续时间
缓存持续到服务器请求的生命周期结束,直到 React 组件树完成渲染。
重新验证
由于记忆不在服务器请求之间共享且仅在渲染期间适用,因此无需重新验证它。
退出
要退出在 fetch
请求中的记忆,可以将 AbortController
signal
传递给请求。
const { signal } = new AbortController()
fetch(url, { signal })
数据缓存
Next.js 具有内置的 Data Cache,它在接收到的服务器请求和部署之间保留数据获取的结果。 这是可能的,因为 Next.js 扩展了本机的 fetch API, 以允许服务器上的每个请求设置自己的持久缓存语义。
在浏览器中,fetch 的 cache
选项指示请求将与浏览器的 HTTP 缓存交互,
而在 Next.js 中,cache
选项指示服务器端请求将与服务器的 Data Cache 交互。
默认情况下,使用 fetch 的数据请求会被缓存。
您可以使用 fetch
的 cache
和 next.revalidate
选项来配置缓存行为。
Data Cache 的工作原理
- 在渲染期间第一次调用
fetch
请求时,Next.js 会检查 Data Cache 是否有缓存的响应。 - 如果找到缓存的响应,则立即返回并进行记忆。
- 如果找不到缓存的响应,则将请求发送到数据源,将结果存储在 Data Cache 中,并进行记忆。
- 对于未缓存的数据(例如
{ cache: 'no-store' }
),始终从数据源获取结果,并进行记忆。 - 无论数据是否被缓存,都会对请求进行记忆,以避免在 React 渲染通行期间为相同的数据发出重复请求。
Data Cache 和请求记忆之间的差异
尽管两种缓存机制都通过重复使用缓存的数据来提高性能, 但 Data Cache 在接收请求和部署之间是持久存在的, 而记忆只在请求的生命周期内存在。
通过记忆,我们减少了在同一渲染通行中必须跨越网络边界从渲染服务器到 Data Cache 服务器(例如 CDN 或 Edge Network)或数据源(例如数据库或 CMS)进行的重复请求的数量。 而通过 Data Cache,我们减少了对原始数据源发出的请求数量。
持续时间
Data Cache 在接收请求和部署之间是持久存在的,除非重新验证或退出。
重新验证
缓存的数据可以通过两种方式进行重新验证:
- 基于时间的重新验证:在经过一定时间并进行新请求之后重新验证数据。这对于数据很少更改且新鲜度不是那么重要的数据非常有用。
- 按需重新验证:基于事件(例如表单提交)重新验证数据。按需重新验证可以使用基于标签或路径的方法一次性重新验证一组数据。当您希望尽快显示最新数据时(例如当您的 headless CMS 中的内容更新时),这非常有用。
基于时间的重新验证
要定时重新验证数据,您可以使用 fetch
的 next.revalidate
选项设置资源的缓存寿命(以秒为单位)。
// 每小时最多重新验证一次
fetch('https://...', { next: { revalidate: 3600 } })
或者,您可以使用 Route Segment Config 选项为段中的所有 fetch
请求配置缓存。
或者,用于无法使用 fetch
的情况。
基于时间的重新验证的工作原理
- 首次带有
revalidate
的fetch
请求时,将从外部数据源获取数据并将其存储在 Data Cache 中。 - 在指定的时间段内(例如 60 秒),调用的任何请求都将返回缓存的数据。
- 在时间段之后,下一个请求仍将返回缓存的(现在是过时的)数据。
- Next.js 将在后台触发数据的重新验证。
- 一旦成功获取数据,Next.js 将使用新数据更新 Data Cache。
- 如果后台重新验证失败,则将保留先前的数据不变。
这类似于陈旧但仍重新验证的行为。
按 需重新验证
数据可以按需通过路径(revalidatePath
)或缓存标签(revalidateTag
)进行重新验证。
按需重新验证的工作原理
- 首次带有重新验证的 fetch 请求时,将从外部数据源获取数据并将其存储在 Data Cache 中。
- 当触发按需重新验证时,将从缓存中清除相应的缓存条目。
- 这与基于时间的重新验证不同,后者将保留缓存中的陈旧数据,直到获取新鲜数据。
- 下一次发出请求时,它将再次是缓存 MISS,数据将从外部数据源获取并存储在 Data Cache 中。
退出
对于单个数据获取,您可以通过将 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 将 Server Components 渲染为一种专为流式传输优化的特殊数据格式,称为 React 服务器组件负载。
- Next.js 使用 React 服务器组件负载和客户端组件 JavaScript 指令来在服务器上渲染 HTML。
这意味着我们不必等待所有内容渲染完毕才缓存工作或发送响应。相反,我们可以在完成工作时流式传输响应。
什么是 React 服务器组件负载?
React 服务器组件负载是呈现的 React 服务器组件树的紧凑二进制表示。 它由 React 在客户端上使用,以更新浏览器的 DOM。React 服务器组件负载包含:
- Server Components 的呈现结果
- 用于渲染 Client Components 以及引用其 JavaScript 文件的占位符
- 从 Server Component 传递给 Client Component 的任何 props
要了解更多,请参阅 Server Components 文档。
2. 服务器上的 Next.js 缓存(完整路由缓存)
完整路由缓存的默认行为,显示了在服务器上缓存静态渲染路由的 React 服务器组件负载和 HTML。 Next.js 的默认行为是在服务器上缓存路由的呈现结果(React 服务器组件负载和 HTML)。这适用于构建时静态渲染的路由,或者在重新验证时。
3. 客户端上的 React 水合和协调
在客户端上,请求时:
- 使用 HTML 立即显示客户端和服务器组件的快速非交互初始预览。
- 使用 React 服务器组件负载来协调客户端和呈现的服务器组件树,并更新 DOM。
- 使用 JavaScript 指令来水合 Client Components 并使应用程序具有交互性。
4. 客户端上的 Next.js 缓存(路由缓存)
React 服务器组件负载存储在客户端侧的路由缓存中 - 一个独立的内存中缓存,按独立路由段拆分。 这个路由缓存用于通过存储先前访问的路由和预取未来路由来改善导航体验。
5. 后续导航
在后续导航或预取期间,Next.js 将检查 React 服务器组件负载是否存储在路由缓存中。 如果是,则它将跳过向服务器发送新请求。
如果路由段不在缓存中,Next.js 将从服务器获取 React 服务器组件负载,并在客户端中填充路由缓存。
静态和动态渲染
路由在构建时是否被缓存取决于它是静态渲染还是动态渲染。 默认情况下,静态路由被缓存,而动态路由在请求时呈现,不被缓存。
此图显示了静态和动态渲染如何影响完整路由缓存,包括缓存和未缓存的数据:
了解更多关于静态和动态渲染。
持续时间
默认情况下,完整路由缓存是持久的。这意味着呈现输出在用户请求之间被缓存。
失效
有两种方式可以使完整路由缓存失效:
- 重新验证数据:重新验证 Data Cache,将通过在服务器上重新渲染组件并缓存新的渲染输出来使 Router Cache 失效。
- 重新部署:与 Data Cache 不同,它跨多个部署保持不变,完整路由缓存在新的部署上被清除。
退出
您可以退出完整路由缓存,换句话说,为每个传入的请求动态呈现组件,方法有:
- 使用动态函数:这将使路由退出完整路由缓存,并在请求时动态呈现。Data Cache 仍然可以使用。
- 使用
dynamic='force-dynamic'
或revalidate=0
路由段配置选项:这将跳过完整路由缓存和 Data Cache。这意味着组件将在每次传入请求到服务器时呈现和获取数据。Router Cache 仍然适用,因为它是客户端缓存。 - 退出 Data Cache:如果一个路由有一个未缓存的 fetch 请求,这将使路由退出完整路由缓存。特定 fetch 请求的数据将在每次传入请求时获取。其他没有退出缓存的 fetch 请求仍然会在 Data Cache 中被缓存。这允许缓存和未缓存数据的混合使用。
路由缓存
相关术语:
您可能会看到 Router Cache 被称为客户端缓存或预取缓存。 虽然预取缓存是指已预取的路由段,但客户端缓存指的是整个 Router 缓存, 其中包括访问过的和预取的段。 此缓存专门适用于 Next.js 和 Server Components,并且与浏览器的 bfcache 不同,尽管其结果类似。
Next.js 具有一个内存中的客户端缓存,用于按独立路由段拆分的 React 服务器组件负载, 持续用户会话的时间称为路由缓存。