以下是不同缓存机制及其用途的概述:
名称 | 是什么 | 在哪里 | 目的 | 生命周期 |
---|---|---|---|---|
Request Memoization | 函数的返回值 | 服务器 | 在 React 组件树中重用数据 | 每个请求的生命周期 |
Data Cache | 数据 | 服务器 | 跨用户请求和部署存储数据 | 持久性(可重新验证) |
Full Route Cache | HTML 和 RSC 有效载荷 | 服务器 | 降低渲染成本并提高性能 | 持久性(可重新验证) |
Router Cache | RSC 有效载荷 | 客户 | 减少导航时的服务器请求 | 用户会话或基于时间 |
默认情况下,Next.js 会尽可能多地缓存,以提高性能并降低成本。这意味着路由会被静态渲染 ,并且数据请求也会被缓存 ,除非您选择退出。
middleware
不支持获取缓存。任何在 middleware
内部执行的获取操作都将被取消缓存。
Request Memoization
Next.js 扩展了 fetch API ,可以自动记忆具有相同 URL 和选项的请求。这意味着你可以在 React 组件树的多个位置针对同一数据调用 fetch 函数,而不必担心通过网络对同一数据进行多次请求所带来的性能影响。
- 在渲染路线时,第一次调用特定请求时,其结果将不会在内存中,并且将是缓存
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,允许服务器上的每个请求设置自己的持久缓存语义。
- 在渲染过程中第一次调用带有
'force-cache'
选项的fetch
请求时,Next.js 会检查数据缓存中是否有缓存的响应 - 如果找到缓存的响应,则立即返回并记忆
- 如果未找到缓存的响应,则向数据源发出请求,将结果存储在数据缓存中并进行记忆
- 对于未缓存的数据(例如,未定义
cache
选项或使用{ cache: 'no-store' }
),结果始终从数据源获取并记忆 - 无论数据是否缓存,请求总是会被记住,以避免在 React 渲染过程中对相同数据进行重复请求
虽然两种缓存机制都可以通过重复使用缓存数据来提高性能,但数据缓存在传入的请求和部署中是持久的,而记忆仅持续请求的整个生命周期
生命周期
除非您重新验证或选择退出,否则数据缓存将在传入请求和部署中持续存在
重新验证
可以通过两种方式重新验证缓存数据:
- 基于时间的重新验证:经过一段时间并发出新请求后重新验证数据。这对于不频繁更改且新鲜度不太重要的数据非常有用。
- 要按时间间隔重新验证数据,您可以使用
fetch
的next.revalidate
选项来设置资源的缓存寿命(以秒为单位) - 或者,您可以使用路由段配置选项来配置段中的所有
fetch
请求,或者在您无法使用fetch
情况下使用基于时间的重新验证 - 第一次调用带有
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 在客户端使用它来更新浏览器的 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
将使路由器缓存无效,并向服务器发出当前路由的新请求