use cache 指令允许您将路由、React 组件或函数标记为可缓存的。它可以在文件顶部使用,以指示文件中的所有导出都应被缓存,也可以在函数或组件顶部内联使用,以缓存返回值。
温馨提示:
- 要使用 cookies 或 headers,请在缓存范围之外读取它们,并将值作为参数传递。这是首选模式。
- 如果内存缓存不足以处理运行时数据,
'use cache: remote'允许平台提供专用的缓存处理器,尽管它需要一次网络往返来检查缓存,并且通常会产生平台费用。- 为了合规性要求,或者当您无法重构代码以将运行时数据作为参数传递给
use cache范围时,请参阅'use cache: private'。
use cache 是一个缓存组件 (Cache Components) 功能。要启用它,请将 cacheComponents 选项添加到您的 next.config.ts 文件中:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig/** @type {import('next').NextConfig} */
const nextConfig = {
cacheComponents: true,
}
module.exports = nextConfig然后,在文件、组件或函数级别添加 use cache:
// File level
'use cache'
export default async function Page() {
// ...
}
// Component level
export async function MyComponent() {
'use cache'
return <></>
}
// Function level
export async function getData() {
'use cache'
const data = await fetch('/api/data')
return data
}温馨提示:在文件级别使用时,所有导出的函数都必须是异步函数。
use cache 的工作原理缓存条目的键是使用其输入的序列化版本生成的,其中包括:
当缓存函数引用外部作用域的变量时,这些变量会自动捕获并绑定为参数,使它们成为缓存键的一部分。
async function Component({ userId }: { userId: string }) {
const getData = async (filter: string) => {
'use cache'
// Cache key includes both userId (from closure) and filter (argument)
return fetch(`/api/users/${userId}/data?filter=${filter}`)
}
return getData('active')
}在上面的代码片段中,userId 从外部作用域捕获,filter 作为参数传递,因此两者都成为 getData 函数缓存键的一部分。这意味着不同的用户和过滤器组合将拥有独立的缓存条目。
缓存函数的参数及其返回值必须是可序列化的。
有关完整参考,请参阅:
温馨提示: 参数和返回值使用不同的序列化系统。服务器组件序列化(用于参数)比客户端组件序列化(用于返回值)更严格。这意味着您可以返回 JSX 元素,但除非使用直通模式,否则不能将它们作为参数接受。
参数:
string、number、boolean、null、undefined{ key: value }[1, 2, 3]返回值:
// Valid - primitives and plain objects
async function UserCard({
id,
config,
}: {
id: string
config: { theme: string }
}) {
'use cache'
return <div>{id}</div>
}
// Invalid - class instance
async function UserProfile({ user }: { user: UserClass }) {
'use cache'
// Error: Cannot serialize class instance
return <div>{user.name}</div>
}您可以接受不可序列化的值,只要您不内省它们。这使得 children 和服务器操作 (Server Actions) 的组合模式成为可能:
async function CachedWrapper({ children }: { children: ReactNode }) {
'use cache'
// Don't read or modify children - just pass it through
return (
<div className="wrapper">
<header>Cached Header</header>
{children}
</div>
)
}
// Usage: children can be dynamic
export default function Page() {
return (
<CachedWrapper>
<DynamicComponent /> {/* Not cached, passed through */}
</CachedWrapper>
)
}您还可以通过缓存组件传递服务器操作 (Server Actions):
async function CachedForm({ action }: { action: () => Promise<void> }) {
'use cache'
// Don't call action here - just pass it through
return <form action={action}>{/* ... */}</form>
}缓存函数和组件不能直接访问运行时 API,例如 cookies()、headers() 或 searchParams。相反,应在缓存范围之外读取这些值,并将它们作为参数传递。
尽管 use cache 主要设计用于在静态外壳中包含动态数据,但它也可以在运行时使用内存中的 LRU(最近最少使用)存储来缓存数据。
运行时缓存行为取决于您的托管环境:
| 环境 | 运行时缓存行为 |
|---|---|
| 无服务器 | 缓存条目通常不会跨请求持久化(每个请求可以是不同的实例)。构建时缓存正常工作。 |
| 自托管 | 缓存条目会在请求之间持久化。使用 cacheMaxMemorySize 控制缓存大小。 |
如果默认的内存缓存不足,请考虑使用 use cache: remote,它允许平台提供专用的缓存处理器(如 Redis 或 KV 数据库)。这有助于减少对未按总流量扩展的数据源的请求,尽管它会产生费用(存储、网络延迟、平台费用)。
极少数情况下,出于合规性要求,或者当您无法重构代码以将运行时数据作为参数传递给 use cache 范围时,您可能需要 use cache: private。
use cache 在运行时在服务器端,缓存条目存储在内存中,并遵循您 cacheLife 配置中的 revalidate 和 expire 时间。您可以通过在 next.config.js 文件中配置 cacheHandlers 来自定义缓存存储。
在客户端,服务器缓存的内容存储在浏览器的内存中,其持续时间由 stale 时间定义。客户端路由器会强制执行至少 30 秒的 stale 时间,无论配置如何。
x-nextjs-stale-time 响应头将缓存生命周期从服务器传达给客户端,确保协调行为。
默认情况下,use cache 使用 default 配置,其设置如下:
async function getData() {
'use cache'
// Implicitly uses default profile
return fetch('/api/data')
}使用 cacheLife 函数自定义缓存持续时间:
import { cacheLife } from 'next/cache'
async function getData() {
'use cache'
cacheLife('hours') // Use built-in 'hours' profile
return fetch('/api/data')
}使用 cacheTag、updateTag 或 revalidateTag 进行按需缓存失效:
import { cacheTag } from 'next/cache'
async function getProducts() {
'use cache'
cacheTag('products')
return fetch('/api/products')
}'use server'
import { updateTag } from 'next/cache'
export async function updateProduct() {
await db.products.update(...)
updateTag('products') // Invalidates all 'products' caches
}cacheLife 和 cacheTag 都跨客户端和服务器缓存层集成,这意味着您在一个地方配置缓存语义,它们将应用于所有地方。
use cache 缓存整个路由要预渲染整个路由,请在 layout 和 page 文件的顶部都添加 use cache。这些段中的每一个都被视为应用程序中的独立入口点,并将独立缓存。
'use cache'
export default async function Layout({ children }: { children: ReactNode }) {
return <div>{children}</div>
}'use cache'
export default async function Layout({ children }) {
return <div>{children}</div>
}任何导入并嵌套在 page 文件中的组件都是与 page 相关联的缓存输出的一部分。
'use cache'
async function Users() {
const users = await fetch('/api/users')
// loop through users
}
export default async function Page() {
return (
<main>
<Users />
</main>
)
}'use cache'
async function Users() {
const users = await fetch('/api/users')
// loop through users
}
export default async function Page() {
return (
<main>
<Users />
</main>
)
}温馨提示:
- 如果
use cache仅添加到layout或page,则只有该路由段以及其中导入的任何组件将被缓存。
use cache 缓存组件的输出您可以在组件级别使用 use cache 以缓存该组件中执行的任何数据获取或计算。只要序列化的 props 在每个实例中生成相同的值,缓存条目就会被重用。
export async function Bookings({ type = 'haircut' }: BookingsProps) {
'use cache'
async function getBookingsData() {
const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
return data
}
return //...
}
interface BookingsProps {
type: string
}export async function Bookings({ type = 'haircut' }) {
'use cache'
async function getBookingsData() {
const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
return data
}
return //...
}use cache 缓存函数输出由于您可以将 use cache 添加到任何异步函数中,因此您不仅限于缓存组件或路由。您可能希望缓存网络请求、数据库查询或慢速计算。
export async function getData() {
'use cache'
const data = await fetch('/api/data')
return data
}export async function getData() {
'use cache'
const data = await fetch('/api/data')
return data
}在 React 中,使用 children 或插槽进行组合是一种构建灵活组件的知名模式。使用 use cache 时,您可以继续以这种方式组合您的 UI。返回的 JSX 中作为 children 或其他组合插槽的任何内容都将通过缓存组件,而不会影响其缓存条目。
只要您不直接引用可缓存函数主体内的任何 JSX 插槽,它们在返回输出中的存在就不会影响缓存条目。
export default async function Page() {
const uncachedData = await getData()
return (
// Pass compositional slots as props, e.g. header and children
<CacheComponent header={<h1>Home</h1>}>
{/* DynamicComponent is provided as the children slot */}
<DynamicComponent data={uncachedData} />
</CacheComponent>
)
}
async function CacheComponent({
header, // header:一个组合插槽,作为 prop 注入
children, // children:另一个用于嵌套组合的插槽
}: {
header: ReactNode
children: ReactNode
}) {
'use cache'
const cachedData = await fetch('/api/cached-data')
return (
<div>
{header}
<PrerenderedComponent data={cachedData} />
{children}
</div>
)
}export default async function Page() {
const uncachedData = await getData()
return (
// Pass compositional slots as props, e.g. header and children
<CacheComponent header={<h1>Home</h1>}>
{/* DynamicComponent is provided as the children slot */}
<DynamicComponent data={uncachedData} />
</CacheComponent>
)
}
async function CacheComponent({
header, // header:一个组合插槽,作为 prop 注入
children, // children:另一个用于嵌套组合的插槽
}) {
'use cache'
const cachedData = await fetch('/api/cached-data')
return (
<div>
{header}
<PrerenderedComponent data={cachedData} />
{children}
</div>
)
}您还可以将服务器操作 (Server Actions) 通过缓存组件传递给客户端组件,而无需在可缓存函数内部调用它们。
import ClientComponent from './ClientComponent'
export default async function Page() {
const performUpdate = async () => {
'use server'
// Perform some server-side update
await db.update(...)
}
return <CachedComponent performUpdate={performUpdate} />
}
async function CachedComponent({
performUpdate,
}: {
performUpdate: () => Promise<void>
}) {
'use cache'
// Do not call performUpdate here
return <ClientComponent action={performUpdate} />
}import ClientComponent from './ClientComponent'
export default async function Page() {
const performUpdate = async () => {
'use server'
// Perform some server-side update
await db.update(...)
}
return <CachedComponent performUpdate={performUpdate} />
}
async function CachedComponent({ performUpdate }) {
'use cache'
// Do not call performUpdate here
return <ClientComponent action={performUpdate} />
}'use client'
export default function ClientComponent({
action,
}: {
action: () => Promise<void>
}) {
return <button onClick={action}>Update</button>
}'use client'
export default function ClientComponent({ action }) {
return <button onClick={action}>Update</button>
}如果您的构建挂起,您正在访问解析为动态或运行时数据的 Promise,这些 Promise 是在 use cache 边界之外创建的。缓存函数会等待在构建期间无法解析的数据,导致在 50 秒后超时。
当构建超时时,您会看到以下错误消息:
Error: Filling a cache during prerender timed out, likely because request-specific arguments such as params, searchParams, cookies() or dynamic data were used inside "use cache".
常见的情况包括:将此类 Promise 作为 props 传递、通过闭包访问它们,或从共享存储(如 Map)中检索它们。
温馨提示: 在
use cache中直接调用cookies()或headers()会立即失败,并出现 不同的错误,而不是超时。
将运行时数据 Promise 作为 props 传递:
import { cookies } from 'next/headers'
import { Suspense } from 'react'
export default function Page() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Dynamic />
</Suspense>
)
}
async function Dynamic() {
const cookieStore = cookies()
return <Cached promise={cookieStore} /> // Build hangs
}
async function Cached({ promise }: { promise: Promise<unknown> }) {
'use cache'
const data = await promise // Waits for runtime data during build
return <p>..</p>
}在 Dynamic 组件中 await cookies 存储,并将 cookie 值传递给 Cached 组件。
共享去重存储:
// Problem: Map stores dynamic Promises, accessed by cached code
import { Suspense } from 'react'
const cache = new Map<string, Promise<string>>()
export default function Page() {
return (
<>
<Suspense fallback={<div>Loading...</div>}>
<Dynamic id="data" />
</Suspense>
<Cached id="data" />
</>
)
}
async function Dynamic({ id }: { id: string }) {
// Stores dynamic Promise in shared Map
cache.set(
id,
fetch(`https://api.example.com/${id}`).then((r) => r.text())
)
return <p>Dynamic</p>
}
async function Cached({ id }: { id: string }) {
'use cache'
return <p>{await cache.get(id)}</p> // Build hangs - retrieves dynamic Promise
}使用 Next.js 内置的 fetch() 去重,或为缓存和未缓存上下文使用单独的 Map。
| 部署选项 | 支持 |
|---|---|
| Node.js 服务器 | 是 |
| Docker 容器 | 是 |
| 静态导出 | 否 |
| 适配器 | 平台特定 |
了解如何在自托管 Next.js 时 配置缓存。
| 版本 | 更改 |
|---|---|
v16.0.0 | "use cache" 通过缓存组件功能启用。 |
v15.0.0 | "use cache" 作为实验性功能引入。 |