SkyBlog

SkyBlog

©2020 - 2025 By Flysky
框架Next.js|主题Shadcn
Built by vercel on Sun, 09 Nov 2025 13:54:54 GMT

Next.js 中的缓存

Next.js 通过缓存渲染工作和数据请求来提升应用性能并降低成本

这篇文章发布于 2025年08月29日,星期五,08:38。阅读 ? 次,0 条评论

以下是不同缓存机制及其用途的概述:

名称是什么在哪里目的生命周期
Request Memoization函数的返回值服务器在 React 组件树中重用数据每个请求的生命周期
Data Cache数据服务器跨用户请求和部署存储数据持久性(可重新验证)
Full Route CacheHTML 和 RSC 有效载荷服务器降低渲染成本并提高性能持久性(可重新验证)
Router CacheRSC 有效载荷客户减少导航时的服务器请求用户会话或基于时间

默认情况下,Next.js 会尽可能多地缓存,以提高性能并降低成本。这意味着路由会被静态渲染 ,并且数据请求也会被缓存 ,除非您选择退出。

路由在构建时静态渲染,以及静态路由首次访问时的默认缓存行为
路由在构建时静态渲染,以及静态路由首次访问时的默认缓存行为

middleware 不支持获取缓存。任何在 middleware 内部执行的获取操作都将被取消缓存。

Request Memoization

Next.js 扩展了 fetch API ,可以自动记忆具有相同 URL 和选项的请求。这意味着你可以在 React 组件树的多个位置针对同一数据调用 fetch 函数,而不必担心通过网络对同一数据进行多次请求所带来的性能影响。

Request Memoization 原理
Request Memoization 原理
  • 在渲染路线时,第一次调用特定请求时,其结果将不会在内存中,并且将是缓存 MISS
  • 因此,将执行该函数,并从外部源获取数据,并将结果存储在内存中
  • 同一次渲染过程中该请求的后续函数调用将会是一次缓存命中 HIT,数据将从内存中返回,而无需执行该函数
  • 一旦路线被渲染并且渲染过程完成,内存就会被“重置”,并且所有请求记忆条目都会被清除
温馨提示
  • 请求记忆化是 React 的功能,而非 Next.js 的功能。此处介绍它是为了展示它如何与其他缓存机制交互
  • 记忆化仅适用于 fetch 请求中的 GET 方法
  • 记忆化仅适用于 React 组件树,这意味着:
    • 它适用于 generateMetadata 、 generateStaticParams 、Layouts、Pages 和其他服务器组件中的 fetch 请求
    • 它不适用于 Route Handlers 中的 fetch 请求,因为它们不是 React 组件树的一部分
  • 对于不适合使用 fetch 的情况(例如某些数据库客户端、CMS 客户端或 GraphQL 客户端),您可以使用 React cache 功能来记忆函数

生命周期

缓存持续服务器请求的整个生命周期,直到 React 组件树完成渲染

重新验证

由于记忆不会在服务器请求之间共享并且仅在渲染期间适用,因此无需重新验证它

Data Cache

Next.js 具有内置数据缓存,可持久保存传入服务器请求和部署的数据提取结果。这是因为 Next.js 扩展了原生的 fetch API,允许服务器上的每个请求设置自己的持久缓存语义。

Data Cache 原理
Data Cache 原理
  • 在渲染过程中第一次调用带有 'force-cache' 选项的 fetch 请求时,Next.js 会检查数据缓存中是否有缓存的响应
  • 如果找到缓存的响应,则立即返回并记忆
  • 如果未找到缓存的响应,则向数据源发出请求,将结果存储在数据缓存中并进行记忆
  • 对于未缓存的数据(例如,未定义 cache 选项或使用 { cache: 'no-store' } ),结果始终从数据源获取并记忆
  • 无论数据是否缓存,请求总是会被记住,以避免在 React 渲染过程中对相同数据进行重复请求
数据缓存和请求记忆之间的差异

虽然两种缓存机制都可以通过重复使用缓存数据来提高性能,但数据缓存在传入的请求和部署中是持久的,而记忆仅持续请求的整个生命周期

生命周期

除非您重新验证或选择退出,否则数据缓存将在传入请求和部署中持续存在

重新验证

可以通过两种方式重新验证缓存数据:

  • 基于时间的重新验证:经过一段时间并发出新请求后重新验证数据。这对于不频繁更改且新鲜度不太重要的数据非常有用。

    • 要按时间间隔重新验证数据,您可以使用 fetch 的 next.revalidate 选项来设置资源的缓存寿命(以秒为单位)

      fetch('https://...', { next: { revalidate: 3600 } })
    • 或者,您可以使用路由段配置选项来配置段中的所有 fetch 请求,或者在您无法使用 fetch 情况下使用

      const dynamic = 'force-dynamic' const fetchCache = 'default-no-store'
      基于时间的重新验证
      基于时间的重新验证
      • 第一次调用带有 revalidate 的获取请求时,数据将从外部数据源获取并存储在数据缓存中
      • 在指定时间范围内(例如 60 秒)调用的任何请求都将返回缓存数据
      • 在该时间范围之后,下一个请求仍将返回缓存的(现在已过时的)数据
        • Next.js 将在后台触发数据的重新验证
        • 一旦成功获取数据,Next.js 将使用新数据更新数据缓存
        • 如果后台重新验证失败,之前的数据将保持不变
  • 按需重新验证: 根据事件(例如表单提交)重新验证数据。按需重新验证可以使用基于标签 revalidateTag 或基于路径 revalidatePath 的方法一次性重新验证多组数据。当您希望确保尽快显示最新数据时(例如,当您的无头 CMS 中的内容更新时),此功能非常有用

    按需重新验证
    按需重新验证
    • 第一次调用 fetch 请求时,数据将从外部数据源获取并存储在数据缓存中
    • 当触发按需重新验证时,相应的缓存条目将从缓存中清除,这与基于时间的重新验证不同,后者将陈旧数据保留在缓存中,直到获取新数据为止
    • 下次请求时,又是缓存 MISS ,会从外部数据源中获取数据,存放到 Data Cache 中

Full Route Cache

Next.js 会在构建时自动渲染并缓存路由。这是一种优化,允许您使用缓存的路由,而不是每次请求都重新在服务器上渲染,从而提高页面加载速度

  • 服务器端 React 渲染 在服务器上,Next.js 使用 React 的 API 来编排渲染。渲染工作被拆分成多个块:通过单独的路由段和 Suspense 边界
    • React 将服务器组件渲染为一种特殊的数据格式,针对流式传输进行了优化,称为 React 服务器组件有效负载
    • Next.js 使用 React 服务器组件有效负载和客户端组件 JavaScript 指令在服务器上呈现 HTML。这意味着我们不必等待所有内容渲染完毕再缓存工作或发送响应。相反,我们可以在工作完成时流式传输响应
React 服务器组件有效负载是什么?

React 服务器组件负载是已渲染的 React 服务器组件树的紧凑二进制表示。React 在客户端使用它来更新浏览器的 DOM。React 服务器组件负载包含:

  • 服务器组件渲染结果
  • 客户端组件渲染位置的占位符以及对其 JavaScript 文件的引用
  • 从服务器组件传递到客户端组件的任何 props
  • Next.js 服务器缓存(完整路由缓存) Next.js 的默认行为是将路由的渲染结果(React 服务器组件负载和 HTML)缓存在服务器上。这适用于构建时或重新验证期间静态渲染的路由
    完整路由缓存
    完整路由缓存
  • 客户端上的 React Hydration 和 Reconciliation
    • HTML 用于立即显示客户端和服务器组件的快速非交互式初始预览
    • React 服务器组件有效负载用于协调客户端和渲染的服务器组件树,并更新 DOM
    • JavaScript 指令用于 水合 客户端组件并使应用程序具有交互性
  • Next.js 客户端缓存(路由器缓存) React 服务器组件的有效负载存储在客户端的路由器缓存中(一个独立的内存缓存),按各个路由段进行拆分。此路由器缓存用于存储先前访问过的路由并预取未来的路由,从而提升导航体验
  • 后续导航 在后续导航或预取过程中,Next.js 会检查 React 服务器组件有效负载是否存储在路由器缓存中。如果是,则不会向服务器发送新的请求;如果路由段不在缓存中,Next.js 将从服务器获取 React Server Components Payload,并填充客户端上的路由器缓存

静态和动态渲染

路由在构建时是否缓存取决于它是静态渲染还是动态渲染。静态路由默认会被缓存,而动态路由在请求时渲染,不会被缓存

静态和动态渲染路由之间的区别,以及缓存和非缓存数据
静态和动态渲染路由之间的区别,以及缓存和非缓存数据

生命周期

默认情况下,完整路由缓存是持久性的。这意味着渲染输出会在用户请求之间进行缓存

缓存无效

  • 重新验证数据:重新验证数据缓存,将通过在服务器上重新渲染组件并缓存新的渲染输出,使路由器缓存无效
  • 重新部署:与跨部署持续存在的数据缓存不同,完整路由缓存在新部署时会被清除

退出缓存

您可以选择退出完整路由缓存,或者换句话说,为每个传入请求动态渲染组件,方式如下:

  • 使用 动态 API:这将使路由从完整路由缓存中退出,并在请求时动态渲染。数据缓存仍然可以使用
  • 使用 dynamic = 'force-dynamic' 或 revalidate = 0 路由段配置选项:这将跳过完整路由缓存和数据缓存。这意味着每次向服务器发出请求时,组件都会被渲染并获取数据。由于路由器缓存是客户端缓存,因此它仍然有效
  • 退出数据缓存:如果路由包含未缓存的 fetch 请求,则该路由将退出完整路由缓存。每次收到请求时,都会获取该 fetch 请求对应的数据。其他明确启用缓存的 fetch 请求仍将缓存在数据缓存中。这允许混合缓存和非缓存数据

Router Cache

Next.js 有一个内存客户端路由器缓存,用于存储路由段的 RSC 有效负载,按布局、加载状态和页面拆分

当用户在路由之间导航时,Next.js 会缓存已访问的路由段,并预取用户可能导航到的路由。这样可以实现即时的前进/后退导航,导航之间无需重新加载整个页面,并且在共享布局中保留浏览器状态和 React 状态

  • 布局被缓存并在导航(部分渲染)时重复使用
  • 加载状态被缓存并在导航中重复使用以实现即时导航
  • 页面默认不缓存,但在浏览器前后导航时会被重复使用。您可以使用实验性的 staleTimes 配置选项来启用页面片段的缓存

生命周期

缓存存储在浏览器的临时内存中。路由器缓存的持续时间由两个因素决定:

  • 会话:缓存在导航过程中持续存在。但是,页面刷新后缓存会被清除
  • 自动失效期:布局和加载状态的缓存会在特定时间后自动失效。失效时间取决于资源的预获取方式以及资源是否为静态生成
    • 默认预取(prefetch={null} 或未指定):动态页面不缓存,静态页面缓存 5 分钟
    • 完全预取(prefetch={true} 或 router.prefetch):静态和动态页面都需要 5 分钟

虽然页面刷新将清除所有缓存的段,但自动失效期仅影响从预取时起的单个段

退出缓存

  • 在服务器操作中:
    • 通过路径 revalidatePath 或通过缓存标签 revalidateTag 按需重新验证数据
    • 使用 cookies.set 或 cookies.delete 会使路由器缓存无效,以防止使用 cookie 的路由变得陈旧(例如身份验证)
  • 调用 router.refresh 将使路由器缓存无效,并向服务器发出当前路由的新请求

下一页

AutoHotkey
  • Request Memoization
  • 生命周期
  • 重新验证
  • Data Cache
  • 生命周期
  • 重新验证
  • Full Route Cache
  • 静态和动态渲染
  • 生命周期
  • 缓存无效
  • 退出缓存
  • Router Cache
  • 生命周期
  • 退出缓存