Menu

use cache

use cache 指令用于指定组件、函数或文件需要被缓存。它可以在文件顶部使用,表示该文件中的所有函数都可以被缓存,也可以在函数顶部内联使用,将该函数标记为可缓存。这是一个实验性的 Next.js 功能,而不是像 use clientuse server 那样的原生 React 功能。

next.config.ts 文件中通过 dynamicIO 标志启用 use cache 指令的支持:

next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  experimental: {
    dynamicIO: true,
  },
}
 
export default nextConfig

use cache 指令未来将与 dynamicIO 标志分开提供。

缓存是一种通过存储计算或数据获取的结果来提高 Web 应用性能的技术。在 Next.js 中,你可以使用缓存来优化应用的渲染性能。

要显式缓存某些异步操作并实现静态行为,你可以使用 use cache 指令。这允许你通过缓存异步数据请求的结果来优化渲染性能,同时在需要时仍能启用动态渲染。

use cache 指令是一个实验性功能,旨在替代 unstable_cache 函数。与 unstable_cache 相比,后者仅限于缓存 JSON 数据,并且需要手动定义重新验证周期和标签,而 use cache 提供了更大的灵活性。它允许你缓存更广泛的数据,包括 React 服务器组件 (RSC) 可以序列化的任何内容,以及数据获取输出和组件输出。

此外,use cache 通过跟踪输入和输出来自动管理复杂性,降低了你意外污染缓存的可能性。由于它同时序列化输入和输出,你可以避免不正确的缓存检索问题。

use cache 指令

Next.js 的 use cache 指令允许你缓存整个路由、组件和函数的返回值。当你有一个异步函数时,你可以通过在文件顶部或函数作用域内添加 use cache 来将其标记为可缓存。这告诉 Next.js 返回值可以被缓存并在后续渲染中重复使用。

// 文件级别
'use cache'
 
export default async function Page() {
  // ...
}
 
// 组件级别
export async function MyComponent() {
  'use cache'
  return <></>
}
 
// 函数级别
export async function getData() {
  'use cache'
  const data = await fetch('/api/data')
  return data
}

值得注意的是:使用 use cache 指令的函数不能有任何副作用,比如修改状态、直接操作 DOM 或设置定时器以定期执行代码

重新验证

默认情况下,当使用 use cache 指令时,Next.js 设置了**15 分钟的重新验证周期**,并且具有近乎无限的过期时间,这意味着它适用于不需要频繁更新的内容。

虽然这对于你不期望经常更改的内容可能有用,但你可以使用 cacheLifecacheTag API 来实现更细粒度的缓存控制:

  • cacheLife:用于基于时间的重新验证周期。
  • cacheTag:用于按需重新验证。

这两个 API 在客户端和服务器端缓存层之间进行集成,这意味着你可以在一个地方配置缓存语义,并使其在任何地方都适用。

基本示例

下面的示例展示了如何在函数级别使用 cacheLife 函数来为函数输出设置一天的重新验证周期:

app/components/my-component.tsx
import { unstable_cacheLife as cacheLife } from 'next/cache'
 
export async function MyComponent() {
  async function getData() {
    'use cache'
    cacheLife('days')
    const data = await fetch('/api/data')
    return data
  }
 
  return // 在这里使用数据
}

缓存重新验证的工作原理

当设置了 15 分钟的重新验证周期时,会发生以下情况:

  1. 缓存命中:如果请求在 15 分钟窗口内发生,则会提供缓存的数据,这是一次缓存命中。
  2. 过期数据:如果请求发生在 15 分钟之后,缓存的值仍然会被提供,但现在被视为过期。Next.js 将在后台重新计算一个新的缓存条目。
  3. 缓存未命中:如果缓存条目过期且发生后续请求,Next.js 将把这视为缓存未命中,数据将被重新计算并从源再次获取。

使用 cacheLife 进行基于时间的重新验证

cacheLife 函数只能在存在 use cache 指令的地方使用,它允许你基于缓存配置文件定义基于时间的重新验证周期。

我们建议在使用 use cache 指令时总是添加缓存配置文件,以显式定义缓存行为。

缓存配置文件是包含以下属性的对象:

属性描述要求
stalenumber客户端应该不检查服务器而缓存值的持续时间。可选
revalidatenumber缓存应该在服务器上刷新的频率;重新验证时可能会提供过期值。可选
expirenumber一个值可以保持过期状态的最长持续时间,超过该时间后将切换到动态获取;必须比 revalidate 更长。可选 - 必须比 revalidate 更长

"stale" 属性与 staleTimes 设置的不同之处在于,它专门控制客户端路由器缓存。虽然 staleTimes 是一个影响动态和静态数据所有实例的全局设置,但 cacheLife 配置允许你在每个函数或每个路由的基础上定义 "stale" 时间。

值得注意的是:"stale" 属性不设置 Cache-control: max-age header。相反,它控制客户端路由器缓存。

默认缓存配置文件

Next.js 提供了一组基于各种时间尺度建模的命名缓存配置文件。如果你在 cacheLife 函数中没有指定缓存配置文件与 use cache 指令一起使用,Next.js 将自动应用 "default" 缓存配置文件。

配置文件StaleRevalidateExpire描述
defaultundefined15 分钟INFINITE_CACHE默认配置文件,适用于不需要频繁更新的内容
secondsundefined1 秒1 分钟适用于需要近实时更新的快速变化内容
minutes5 分钟1 分钟1 小时适用于在一小时内频繁更新的内容
hours5 分钟1 小时1 天适用于每天更新但可以稍微过期的内容
days5 分钟1 天1 周适用于每周更新但可以过期一天的内容
weeks5 分钟1 周1 个月适用于每月更新但可以过期一周的内容
max5 分钟1 个月INFINITE_CACHE适用于很少需要更新的非常稳定的内容

基本示例

app/page.tsx
'use cache'
import { unstable_cacheLife as cacheLife } from 'next/cache'
 
cacheLife('minutes')

用于引用缓存配置文件的字符串值本身并不携带固有含义;相反,它们作为语义标签。这使你能够更好地理解和管理代码库中的缓存内容。

定义可重用的缓存配置文件

要创建可重用的缓存配置文件,请选择适合你的用例的名称。你可以根据需要创建任意数量的自定义缓存配置文件。每个配置文件都可以通过其名称作为字符串值传递给 cacheLife 函数。

next.config.ts
const nextConfig = {
  experimental: {
    dynamicIO: true,
    cacheLife: {
      biweekly: {
        stale: 60 * 60 * 24 * 14, // 14 天
        revalidate: 60 * 60 * 24, // 1 天
        expire: 60 * 60 * 24 * 14, // 14 天
      },
    },
  },
}
 
module.exports = nextConfig

上面的示例缓存 14 天,每天检查更新,并在 14 天后使缓存过期。然后,你可以通过其名称在整个应用中引用此配置文件:

app/page.tsx
'use cache'
import { unstable_cacheLife as cacheLife } from 'next/cache'
 
cacheLife('biweekly')
 
// 剩余代码

覆盖默认缓存配置文件

虽然默认缓存配置文件提供了一种有用的方式来思考任何给定的可缓存输出的新鲜度或过期程度,但你可能更喜欢不同的命名配置文件,以更好地与你的应用缓存策略保持一致。

你可以通过创建与默认值同名的新配置来覆盖默认命名缓存配置文件。

下面的示例展示了如何覆盖默认的 "days" 缓存配置文件:

next.config.ts
const nextConfig = {
  experimental: {
    dynamicIO: true,
    cacheLife: {
      days: {
        stale: 3600, // 1 小时
        revalidate: 900, // 15 分钟
        expire: 86400, // 1 天
      },
    },
  },
}
 
module.exports = nextConfig

定义内联缓存配置文件

对于特定用例,你可以通过将对象传递给 cacheLife 函数来设置自定义缓存配置文件:

app/page.tsx
'use cache'
import { unstable_cacheLife as cacheLife } from 'next/cache'
 
cacheLife({
  stale: 3600, // 1 小时
  revalidate: 900, // 15 分钟
  expire: 86400, // 1 天
})
 
// 剩余代码

此内联缓存配置文件只会应用于创建它的函数或文件。如果你想在整个应用中重用相同的配置文件,你可以将配置添加到 next.config.ts 文件的 cacheLife 属性中。

use cachecacheLife 的嵌套使用

当在相同的路由或组件树中定义多个缓存行为时,如果内部缓存指定了它们自己的 cacheLife 配置文件,外部缓存将遵循它们之间最短的缓存持续时间。这仅适用于外部缓存没有定义自己的显式 cacheLife 配置文件的情况。

缓存边界的决策层次:

  1. Next.js 将使用在整个 use cache 边界内找到的最短缓存配置文件,不包括内部 use cache 指令。
  2. 如果不存在缓存配置文件,则所有内部 use cache 调用中的最短配置文件时间将应用于此 use cache。如果没有内部 use cache,则使用默认值。
  3. 两层深的内部缓存不会影响外部缓存,因为它们已经将其持续时间提供给了它们的父级。

例如,如果你在页面中添加 use cache 指令,但没有指定缓存配置文件,则默认缓存配置文件将被隐式应用(cacheLife("default"))。如果导入到页面中的组件也使用 use cache 指令并有自己的缓存配置文件,则外部和内部缓存配置文件会进行比较,并且配置文件中设置的最短持续时间将被应用。

app/components/parent.tsx
// 父组件
import { unstable_cacheLife as cacheLife } from 'next/cache'
 
export async function ParentComponent() {
  'use cache'
  cacheLife('days')
 
  return (
    <div>
      <ChildComponent />
    </div>
  )
}
 
// 子组件
import { unstable_cacheLife as cacheLife } from 'next/cache'
 
export async function ChildComponent() {
  'use cache'
  cacheLife('hours')
 
  // 该组件的缓存将遵循较短的 'hours' 配置文件
}

使用 cacheTag 按需重新验证

cacheTagrevalidateTag 结合使用,用于按需清除缓存数据。cacheTag 函数接受单个字符串值或字符串数组。

在下面的示例中,getData 函数使用 "weeks" 缓存配置文件,并在函数的缓存输出上定义了 cacheTag

app/actions.ts
import {
  unstable_cacheTag as cacheTag,
  unstable_cacheLife as cacheLife,
} from 'next/cache'
 
export async function getData() {
  'use cache'
  cacheLife('weeks')
  cacheTag('my-data')
 
  const data = await fetch('/api/data')
  return data
}

然后,你可以使用 revalidateTag 在另一个函数中按需清除缓存,例如在路由处理程序服务器操作中:

app/submit.ts
'use server'
 
import { revalidateTag } from 'next/cache'
 
export default async function submit() {
  await addPost()
  revalidateTag('my-data')
}

有关按需清除缓存数据的更多信息,请参阅 revalidateTag 文档。

示例

使用 use cache 缓存整个路由

Suspense 边界在你的应用中的位置决定了你的组件可以有多动态。Suspense 边界内的组件允许是动态的,但这并不意味着它们自动就是动态的。如果你缓存所有内容或你的内容是静态的,Next.js 仍然会生成一个静态应用。使用 Suspense 表明在边界内允许动态行为。

要确保你的路由保持静态,避免使用 Suspense 边界。如果你必须使用它们,你可以通过将 use cache 指令同时添加到布局和页面组件来维持静态页面,因为它们在你的应用中被视为独立的入口点。

这建议用于之前使用 export const dynamic = "force-cache" 选项的应用,并将确保整个路由被预渲染。

app/layout.tsx
"use cache"
import { unstable_cacheLife as cacheLife } from 'next/cache'
cacheLife('minutes')
 
export default Layout({children}: {children: ReactNode}) {
  return <div>{children}</div>
}

在你的 page.tsx 文件中,你可以在文件顶部添加 use cache 指令,并定义缓存配置文件:

app/page.tsx
"use cache"
import { unstable_cacheLife as cacheLife } from 'next/cache'
cacheLife('minutes')
 
async function Users() {
  const users = await fetch('/api/users');
  // 遍历用户
}
 
export default Page() {
  return (
    <main>
      <Users/>
    </main>
  )
}

使用 use cache 缓存组件输出

你可以在组件级别使用 use cache 来缓存该组件内执行的任何获取或计算。当你在整个应用中重用该组件时,只要 props 保持相同的结构,它就可以共享相同的缓存条目。

props 会被序列化并构成缓存键的一部分。如果你在应用的多个地方使用相同的组件,只要序列化的 props 在每个实例中生成相同的值,缓存条目就会被重用。

app/components/bookings.tsx
import { unstable_cacheLife as cacheLife } from 'next/cache'
 
interface BookingsProps {
  type: string
}
 
export async function Bookings({ type = 'massage' }: BookingsProps) {
  'use cache'
  cacheLife('minutes')
 
  async function getBookingsData() {
    const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
    return data
  }
  return //...
}

使用 use cache 缓存函数输出

由于你可以将 use cache 添加到任何异步函数,因此你不仅限于缓存组件或路由。你可能想要缓存网络请求、数据库查询或计算非常慢的内容。通过将 use cache 添加到包含这类工作的函数中,它就变得可缓存,当重复使用时,将共享相同的缓存条目。

app/actions.ts
import { unstable_cacheLife as cacheLife } from 'next/cache'
 
export async function getData() {
  'use cache'
  cacheLife('minutes')
 
  const data = await fetch('/api/data')
  return data
}