Next.js 通过缓存渲染工作和数据请求来提高应用程序性能并降低成本。本页面深入探讨 Next.js 的缓存机制、可用于配置它们的 API 以及它们之间如何相互作用。
须知:本页面旨在帮助您理解 Next.js 的底层工作原理,但对于使用 Next.js 进行开发而言,这并非必要知识。Next.js 的大多数缓存启发式机制都由您的 API 使用方式决定,并默认采用零配置或最少配置以实现最佳性能。如果您想直接查看示例,请从这里开始。
以下是不同缓存机制及其用途的高级概述:
| 机制 | 作用 | 位置 | 目的 | 持续时间 |
|---|---|---|---|---|
| 请求记忆化 (Request Memoization) | 函数的返回值 | 服务器 | 在 React 组件树中复用数据 | 每个请求的生命周期 |
| 数据缓存 (Data Cache) | 数据 | 服务器 | 跨用户请求和部署存储数据 | 持久性(可重新验证) |
| 完整路由缓存 (Full Route Cache) | HTML 和 RSC payload | 服务器 | 降低渲染成本并提升性能 | 持久性(可重新验证) |
| 路由器缓存 (Router Cache) | RSC Payload | 客户端 | 减少导航时的服务器请求 | 用户会话或基于时间 |
默认情况下,Next.js 会尽可能多地进行缓存以提高性能并降低成本。这意味着除非您选择退出,否则路由会静态渲染,数据请求会缓存。下图展示了默认的缓存行为:路由在构建时静态渲染以及静态路由首次访问时的行为。

缓存行为会根据路由是静态渲染还是动态渲染、数据是缓存还是未缓存,以及请求是初始访问还是后续导航而变化。根据您的用例,您可以为单个路由和数据请求配置缓存行为。
proxy 中不支持 fetch 缓存。在您的 proxy 内部进行的任何 fetch 操作都将不会被缓存。
要理解 Next.js 中缓存的工作原理,了解可用的渲染策略会有所帮助。渲染策略决定了您的路由 HTML 何时生成,这直接影响到哪些内容可以被缓存。
通过静态渲染,路由在构建时或在数据重新验证后在后台渲染。结果会被缓存,并可在跨请求中复用。静态路由在完整路由缓存中被完全缓存。
通过动态渲染,路由在请求时渲染。当您的路由使用请求特定信息,例如 cookies、headers 或 search params 时,就会发生这种情况。
当路由使用以下任一 API 时,它会变为动态:
cookiesheadersconnectiondraftModesearchParams propunstable_noStorefetch 带有 { cache: 'no-store' } 选项动态路由不会在完整路由缓存中缓存,但仍可将数据缓存用于数据请求。
须知:您可以使用缓存组件在同一路由中混合静态和动态渲染。
Next.js 扩展了 fetch API 以自动记忆化具有相同 URL 和选项的请求。这意味着您可以在 React 组件树中的多个位置调用相同的 fetch 函数来获取相同的数据,但只执行一次。

例如,如果您需要在整个路由(例如在 Layout、Page 和多个组件中)使用相同的数据,则无需在树的顶部获取数据,并在组件之间转发 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 HITasync 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请求记忆化 (Request Memoization) 的工作原理

MISS。HIT,数据将从内存中返回,而无需执行函数。须知:
- 请求记忆化是 React 的一个特性,而不是 Next.js 的特性。此处包含它是为了展示它如何与其他缓存机制交互。
- 记忆化仅适用于
fetch请求中的GET方法。- 记忆化仅适用于 React 组件树,这意味着:
- 它适用于
generateMetadata、generateStaticParams、Layouts、Pages和其他Server Components中的fetch请求。- 它不适用于
Route Handlers中的fetch请求,因为它们不是 React 组件树的一部分。- 对于
fetch不适用(例如某些数据库客户端、CMS 客户端或 GraphQL 客户端)的情况,您可以使用 Reactcache函数来记忆化函数。
缓存持续服务器请求的整个生命周期,直到 React 组件树完成渲染。
由于记忆化不跨服务器请求共享,且仅在渲染期间适用,因此无需对其进行重新验证。
记忆化仅适用于 fetch 请求中的 GET 方法,其他方法(如 POST 和 DELETE)不会被记忆化。这种默认行为是 React 的一项优化,我们不建议选择退出。
要管理单个请求,您可以使用 AbortController 中的 signal 属性。
const { signal } = new AbortController();
fetch(url, { signal });Next.js 内置了一个数据缓存,可将数据 fetch 的结果持久化到后续的服务器请求和部署中。这是因为 Next.js 扩展了原生的 fetch API,允许服务器上的每个请求设置自己的持久缓存语义。
须知:在浏览器中,
fetch的cache选项表示请求将如何与浏览器的 HTTP 缓存交互;而在 Next.js 中,cache选项表示服务器端请求将如何与服务器的数据缓存交互。
您可以使用 fetch 的 cache 和 next.revalidate 选项来配置缓存行为。
在开发模式下,fetch 数据会重用于热模块替换 (HMR),并且缓存选项会因硬刷新而被忽略。
数据缓存 (Data Cache) 的工作原理

'force-cache' 选项的 fetch 请求在渲染期间首次被调用时,Next.js 会检查数据缓存中是否存在缓存响应。cache 选项或使用 { cache: 'no-store' }),结果始终从数据源获取并被记忆化。数据缓存与请求记忆化 (Request Memoization) 的区别
尽管这两种缓存机制都通过复用缓存数据来帮助提高性能,但数据缓存会持久化地跨入站请求和部署,而记忆化仅在请求的生命周期内有效。
数据缓存会持久化跨入站请求和部署,除非您重新验证或选择退出。
缓存数据可以通过两种方式进行重新验证:
要以定时间隔重新验证数据,您可以使用 fetch 的 next.revalidate 选项来设置资源的缓存生命周期(以秒为单位)。
// Revalidate at most every hour
fetch("https://...", { next: { revalidate: 3600 } });或者,您可以使用路由段配置选项来配置段中的所有 fetch 请求,或用于无法使用 fetch 的情况。
基于时间的重新验证的工作原理

revalidate 的 fetch 请求时,数据将从外部数据源获取并存储在数据缓存中。这类似于 stale-while-revalidate 行为。
数据可以根据路径(revalidatePath)或根据缓存标签(revalidateTag)按需重新验证。
按需重新验证的工作原理

fetch 请求时,数据将从外部数据源获取并存储在数据缓存中。MISS,数据将从外部数据源获取并存储在数据缓存中。如果您 不 希望缓存 fetch 的响应,您可以执行以下操作:
let data = await fetch("https://api.vercel.app/blog", { cache: "no-store" });相关术语:
您可能会看到 Automatic Static Optimization(自动静态优化)、Static Site Generation(静态站点生成)或 Static Rendering(静态渲染)等术语交替使用,它们都指代在构建时渲染和缓存应用程序路由的过程。
Next.js 会在构建时自动渲染和缓存路由。这是一种优化,允许您直接提供缓存的路由,而不是每次请求都在服务器上重新渲染,从而实现更快的页面加载。
要理解完整路由缓存的工作原理,了解 React 如何处理渲染以及 Next.js 如何缓存结果会很有帮助:
在服务器上,Next.js 使用 React 的 API 来协调渲染。渲染工作被分割成块:按单个路由段和 Suspense 边界。
每个块分两步渲染:
Server Components 渲染成一种特殊的数据格式,该格式经过优化以支持流式传输,称为 React Server Component Payload。Client Component JavaScript 指令在服务器上渲染 HTML。这意味着我们不必等到所有内容都渲染完毕才缓存工作或发送响应。相反,我们可以在工作完成后立即流式传输响应。
什么是 React Server Component Payload?
React Server Component Payload 是渲染后的 React Server Components 树的紧凑二进制表示。它由客户端上的 React 用来更新浏览器的 DOM。React Server Component Payload 包含:
Server Components的渲染结果Client Components应该渲染的位置的占位符以及对其 JavaScript 文件的引用- 从
Server Component传递到Client Component的任何 props要了解更多信息,请参阅 Server Components 文档。

Next.js 的默认行为是在服务器上缓存路由的渲染结果(React Server Component Payload 和 HTML)。这适用于构建时静态渲染的路由,或在重新验证期间。
在请求时,客户端:
Client 和 Server Components 的快速非交互式初始预览。Client 和已渲染的 Server Component 树,并更新 DOM。Client Components 并使应用程序具有交互性。React Server Component Payload 存储在客户端路由器缓存中——一个独立的内存缓存,按独立的路由段(布局、加载状态和页面)进行分割。此路由器缓存用于通过存储先前访问的路由和预取未来的路由来改善导航体验。
在后续导航或预取期间,Next.js 将检查 React Server Components Payload 是否存储在路由器缓存中。如果是,它将跳过向服务器发送新请求。
如果路由段不在缓存中,Next.js 将从服务器获取 React Server Components Payload,并在客户端填充路由器缓存。
路由是否在构建时缓存取决于它是静态渲染还是动态渲染。静态路由默认缓存,而动态路由在请求时渲染,不进行缓存。
此图显示了静态和动态渲染路由之间的差异,以及缓存和未缓存数据:

了解更多关于静态和动态渲染。
默认情况下,完整路由缓存是持久的。这意味着渲染输出会在用户请求之间进行缓存。
您可以通过两种方式使完整路由缓存失效:
您可以通过以下方式选择退出完整路由缓存,换句话说,为每个传入请求动态渲染组件:
dynamic = 'force-dynamic' 或 revalidate = 0 路由段配置选项:这将跳过完整路由缓存和数据缓存。这意味着组件将在服务器的每个传入请求时渲染并获取数据。路由器缓存仍然适用,因为它是一个客户端缓存。fetch 请求,这将使该路由选择退出完整路由缓存。该特定 fetch 请求的数据将在每个传入请求时获取。其他明确启用缓存的 fetch 请求仍将缓存在数据缓存中。这允许缓存和未缓存数据的混合。Next.js 有一个内存中的客户端路由器缓存,它存储按布局、加载状态和页面划分的路由段的 RSC payload。
当用户在路由之间导航时,Next.js 会缓存已访问的路由段并预取用户可能要导航到的路由。这会带来即时的前进/后退导航、导航之间没有整页重新加载,并保持浏览器状态和共享布局中的 React 状态。
通过路由器缓存:
staleTimes 配置选项来为页面段启用缓存。须知:此缓存专门适用于 Next.js 和
Server Components,与浏览器的 bfcache 不同,尽管它具有相似的结果。
缓存存储在浏览器的临时内存中。两个因素决定了路由器缓存的持续时间:
prefetch={null} 或未指定):动态页面不缓存,静态页面缓存 5 分钟。prefetch={true} 或 router.prefetch):静态和动态页面均缓存 5 分钟。虽然页面刷新会清除所有缓存的段,但自动失效期仅影响从预取时开始的单个段。
须知:实验性的
staleTimes配置选项可用于调整上述自动失效时间。
您可以通过两种方式使路由器缓存失效:
revalidatePath) 或通过缓存标签 (revalidateTag) 按需重新验证数据cookies.set 或 cookies.delete 会使路由器缓存失效,以防止使用 cookies 的路由变得过时(例如,反映身份验证更改)。router.refresh 将使路由器缓存失效,并向服务器发出当前路由的新请求。从 Next.js 15 开始,页面段默认选择退出缓存。
须知:您还可以通过将
<Link>组件的prefetchprop 设置为false来选择退出预取。
在配置不同的缓存机制时,了解它们之间如何相互作用至关重要:
revalidatePath 或 revalidateTag。下表概述了不同的 Next.js API 如何影响缓存:
| API | 路由器缓存 | 完整路由缓存 | 数据缓存 | React 缓存 |
|---|---|---|---|---|
<Link prefetch> | 缓存 | |||
router.prefetch | 缓存 | |||
router.refresh | 重新验证 | |||
fetch | 缓存 | 缓存 (GET 和 HEAD) | ||
fetch options.cache | 缓存或选择退出 | |||
fetch options.next.revalidate | 重新验证 | 重新验证 | ||
fetch options.next.tags | 缓存 | 缓存 | ||
revalidateTag | 重新验证 (Server Action) | 重新验证 | 重新验证 | |
revalidatePath | 重新验证 (Server Action) | 重新验证 | 重新验证 | |
const revalidate | 重新验证或选择退出 | 重新验证或选择退出 | ||
const dynamic | 缓存或选择退出 | 缓存或选择退出 | ||
cookies | 重新验证 (Server Action) | 选择退出 | ||
headers, searchParams | 选择退出 | |||
generateStaticParams | 缓存 | |||
React.cache | 缓存 | |||
unstable_cache | 缓存 |
<Link>默认情况下,<Link> 组件会自动从完整路由缓存中预取路由,并将 React Server Component Payload 添加到路由器缓存中。
要禁用预取,您可以将 prefetch prop 设置为 false。但这不会永久跳过缓存,当用户访问路由时,路由段仍会在客户端进行缓存。
了解更多关于 <Link> 组件的信息。
router.prefetchuseRouter 钩子的 prefetch 选项可用于手动预取路由。这将把 React Server Component Payload 添加到路由器缓存中。
请参阅 useRouter 钩子 API 参考。
router.refreshuseRouter 钩子的 refresh 选项可用于手动刷新路由。这将完全清除路由器缓存,并向服务器发出当前路由的新请求。refresh 不会影响数据缓存或完整路由缓存。
渲染结果将在客户端进行协调,同时保留 React 状态和浏览器状态。
请参阅 useRouter 钩子 API 参考。
fetch从 fetch 返回的数据不会自动缓存到数据缓存中。
默认情况下,当未提供 cache 或 next.revalidate 选项时:
请参阅 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 和 revalidateTagNext.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,具体取决于您要实现的目标:
revalidatePathrevalidatePath 允许您通过一次操作手动重新验证数据并重新渲染特定路径下的路由段。调用 revalidatePath 方法会重新验证数据缓存,进而使完整路由缓存失效。
revalidatePath("/");您可以在两个地方使用 revalidatePath,具体取决于您要实现的目标:
请参阅 revalidatePath API 参考了解更多信息。
revalidatePath与router.refresh:调用
router.refresh将清除路由器缓存,并在服务器上重新渲染路由段,而不会使数据缓存或完整路由缓存失效。区别在于
revalidatePath清除数据缓存和完整路由缓存,而router.refresh()不会更改数据缓存和完整路由缓存,因为它是一个客户端 API。
像 cookies 和 headers 这样的动态 API,以及 Pages 中的 searchParams prop,都依赖于运行时传入的请求信息。使用它们将使路由选择退出完整路由缓存,换句话说,路由将被动态渲染。
cookies在 Server Action 中使用 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 或匹配(在捕获所有路由的情况下)。
cache 函数React cache 函数允许您记忆化函数的返回值,从而允许您多次调用同一个函数,但只执行一次。
使用 GET 或 HEAD 方法的 fetch 请求会自动记忆化,因此您无需将其包装在 React cache``中。但是,对于其他fetch方法,或在使用不固有记忆化请求的数据获取库(例如某些数据库、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;
});