Menu

use cache: remote

'use cache: remote' 指令在常规 use cache 无法工作的动态上下文中启用共享数据的缓存,例如在调用 await connection()await cookies()await headers() 之后。

值得注意的是

使用方法

要使用 'use cache: remote',请在你的 next.config.ts 文件中启用 cacheComponents 标志:

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

然后在需要在动态上下文中缓存数据的函数中添加 'use cache: remote'

基本示例

缓存需要在请求时获取但可以在所有用户之间共享的产品价格。使用 cacheLife 设置价格的缓存生命周期。

app/product/[id]/page.tsx
TypeScript
import { Suspense } from 'react'
import { connection } from 'next/server'
import { cacheTag, cacheLife } from 'next/cache'
 
export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
 
  return (
    <div>
      <ProductDetails id={id} />
      <Suspense fallback={<div>加载价格中...</div>}>
        <ProductPrice productId={id} />
      </Suspense>
    </div>
  )
}
 
function ProductDetails({ id }: { id: string }) {
  return <div>产品:{id}</div>
}
 
async function ProductPrice({ productId }: { productId: string }) {
  // 调用 connection() 使此组件变为动态,防止
  // 它被包含在静态外壳中。这确保价格
  // 始终在请求时获取。
  await connection()
 
  // 现在我们可以在远程缓存处理器中缓存价格。
  // 常规 'use cache' 在这里不起作用,因为我们处于动态上下文中。
  const price = await getProductPrice(productId)
 
  return <div>价格:${price}</div>
}
 
async function getProductPrice(productId: string) {
  'use cache: remote'
  cacheTag(`product-price-${productId}`)
  cacheLife({ expire: 3600 }) // 1 小时
 
  // 此数据库查询被缓存并在所有用户之间共享
  return db.products.getPrice(productId)
}

注意: 常规 use cache 在动态上下文中使用时不会缓存任何内容(在 await connection()await cookies()await headers() 等之后)。使用 'use cache: remote' 在这些场景中启用运行时缓存。

use cache: remoteuse cacheuse cache: private 的区别

Next.js 提供三种缓存指令,每种都针对不同的用例设计:

特性use cache'use cache: remote''use cache: private'
在动态上下文中工作否(需要静态上下文)是(专为动态上下文设计)
访问 await cookies()
访问 await headers()
await connection() 之后否(不会缓存)
存储在缓存处理器中是(服务器端)是(服务器端)否(仅客户端)
缓存作用域全局(共享)全局(共享)每用户(隔离)
支持运行时预取不适用(在构建时预渲染)是(配置时)
用例静态、共享内容(构建时)运行时上下文中的动态、共享内容(每请求)个性化、用户特定内容

注意: 虽然你不能在 'use cache: remote' 内部调用 await cookies()await headers(),但你可以在调用被 'use cache: remote' 包装的函数之前读取这些值,参数将被包含在缓存键中。请注意,这不推荐,因为它会显著增加缓存大小并降低缓存命中率。

何时使用每种指令

根据你的用例选择正确的缓存指令:

使用 use cache 当:

  • 内容可以在构建时预渲染
  • 内容在所有用户之间共享
  • 内容不依赖于请求特定的数据

使用 'use cache: remote' 当:

  • 你需要在动态上下文中缓存
  • 内容在用户之间共享,但必须按请求渲染(在 await connection() 之后)
  • 你想在服务器端缓存处理器中缓存昂贵的操作

使用 'use cache: private' 当:

  • 内容是每用户个性化的(依赖于 cookies、headers)
  • 你需要用户特定内容的运行时预取
  • 内容绝不应在用户之间共享

工作原理

'use cache: remote' 指令通过将结果存储在服务器端缓存处理器而不是在构建时预渲染,在动态上下文中启用共享数据的运行时缓存。

动态上下文检测

当 Next.js 遇到某些 API(如 connection()cookies()headers())时,上下文变为"动态"。在动态上下文中:

  1. 常规 use cache 停止工作 - 它不会缓存任何内容
  2. 'use cache: remote' 继续工作 - 它由远程缓存处理器缓存。
  3. 结果存储在服务器端,存储在为你的部署配置的键值存储中
  4. 缓存数据在请求之间共享 - 减少数据库负载和源请求

值得注意的是: 如果没有 'use cache: remote',动态上下文中的函数将在每次请求时执行,可能会造成性能瓶颈。远程缓存通过将结果存储在服务器端缓存处理器中来消除此问题。

存储行为

远程缓存使用服务器端缓存处理器持久化,可能包括:

  • 分布式键值存储(内存或持久存储解决方案)
  • 文件系统或内存存储(通常用于开发或自定义部署)
  • 环境特定的缓存(由你的托管基础设施提供)
  • 自定义或配置的缓存处理器(取决于你的应用程序设置)

这意味着:

  1. 缓存数据在所有用户和请求之间共享
  2. 缓存条目在单个会话之外持久存在
  3. 缓存失效通过 cacheTagrevalidateTag 工作
  4. 缓存过期由 cacheLife 配置控制

动态上下文示例

async function UserDashboard() {
  // 调用 connection() 使上下文变为动态
  await connection()
 
  // 没有任何缓存指令,这在每次请求时运行
  const stats = await getStats()
 
  // 使用 'use cache: remote',这在远程处理器中缓存
  const analytics = await getAnalytics()
 
  return (
    <div>
      <Stats data={stats} />
      <Analytics data={analytics} />
    </div>
  )
}
 
async function getAnalytics() {
  'use cache: remote'
  cacheLife({ expire: 300 }) // 5 分钟
 
  // 这个昂贵的操作被缓存并在所有请求之间共享
  return fetchAnalyticsData()
}

请求 API 和远程缓存

虽然 'use cache: remote' 技术上允许通过在调用被 'use cache: remote' 包装的函数之前调用 cookies()headers() 等 API 来访问请求特定的数据,但通常不建议将它们一起使用:

APIuse cache 中允许'use cache: remote' 中允许推荐
cookies()使用 'use cache: private' 代替
headers()使用 'use cache: private' 代替
connection()否 - 这些永远无法被缓存
searchParams使用 'use cache: private' 代替

重要: 如果你需要基于 cookies、headers 或搜索参数进行缓存,请使用 'use cache: private'。远程缓存在所有用户之间共享,因此在其中缓存用户特定数据可能导致向不同用户提供不正确的结果。

嵌套规则

远程缓存有特定的嵌套规则:

  • 远程缓存可以嵌套在其他远程缓存内部('use cache: remote'
  • 远程缓存可以嵌套在常规缓存内部('use cache'
  • 远程缓存不能嵌套在私有缓存内部('use cache: private'
  • 私有缓存不能嵌套在远程缓存内部
// 有效:远程在远程内部
async function outerRemote() {
  'use cache: remote'
  const result = await innerRemote()
  return result
}
 
async function innerRemote() {
  'use cache: remote'
  return getData()
}
 
// 有效:远程在常规缓存内部
async function outerCache() {
  'use cache'
  // 如果这处于动态上下文中,内部远程缓存将工作
  const result = await innerRemote()
  return result
}
 
async function innerRemote() {
  'use cache: remote'
  return getData()
}
 
// 无效:远程在私有内部
async function outerPrivate() {
  'use cache: private'
  const result = await innerRemote() // 错误!
  return result
}
 
async function innerRemote() {
  'use cache: remote'
  return getData()
}
 
// 无效:私有在远程内部
async function outerRemote() {
  'use cache: remote'
  const result = await innerPrivate() // 错误!
  return result
}
 
async function innerPrivate() {
  'use cache: private'
  return getData()
}

示例

以下示例演示了使用 'use cache: remote' 的常见模式。有关 cacheLife 参数(stalerevalidateexpire)的详细信息,请参阅 cacheLife API 参考

每请求数据库查询

缓存在动态上下文中访问的昂贵数据库查询,减少数据库负载:

app/dashboard/page.tsx
import { connection } from 'next/server'
import { cacheLife, cacheTag } from 'next/cache'
 
export default async function DashboardPage() {
  // 使上下文变为动态
  await connection()
 
  const stats = await getGlobalStats()
 
  return <StatsDisplay stats={stats} />
}
 
async function getGlobalStats() {
  'use cache: remote'
  cacheTag('global-stats')
  cacheLife({ expire: 60 }) // 1 分钟
 
  // 这个昂贵的数据库查询被缓存并在所有用户之间共享,
  // 减少数据库负载
  const stats = await db.analytics.aggregate({
    total_users: 'count',
    active_sessions: 'count',
    revenue: 'sum',
  })
 
  return stats
}

流式上下文中的 API 响应

缓存在流式传输期间或动态操作之后获取的 API 响应:

app/feed/page.tsx
import { Suspense } from 'react'
import { connection } from 'next/server'
import { cacheLife, cacheTag } from 'next/cache'
 
export default async function FeedPage() {
  return (
    <div>
      <Suspense fallback={<Skeleton />}>
        <FeedItems />
      </Suspense>
    </div>
  )
}
 
async function FeedItems() {
  // 动态上下文
  await connection()
 
  const items = await getFeedItems()
 
  return items.map((item) => <FeedItem key={item.id} item={item} />)
}
 
async function getFeedItems() {
  'use cache: remote'
  cacheTag('feed-items')
  cacheLife({ expire: 120 }) // 2 分钟
 
  // 此 API 调用被缓存,减少对外部服务的请求
  const response = await fetch('https://api.example.com/feed')
  return response.json()
}

动态检查后的计算数据

缓存在动态安全或功能检查之后发生的昂贵计算:

app/reports/page.tsx
import { connection } from 'next/server'
import { cacheLife } from 'next/cache'
 
export default async function ReportsPage() {
  // 动态安全检查
  await connection()
 
  const report = await generateReport()
 
  return <ReportViewer report={report} />
}
 
async function generateReport() {
  'use cache: remote'
  cacheLife({ expire: 3600 }) // 1 小时
 
  // 这个昂贵的计算被缓存并在所有授权用户之间共享,
  // 避免重复计算
  const data = await db.transactions.findMany()
 
  return {
    totalRevenue: calculateRevenue(data),
    topProducts: analyzeProducts(data),
    trends: calculateTrends(data),
  }
}

混合缓存策略

结合静态、远程和私有缓存以获得最佳性能:

app/product/[id]/page.tsx
import { Suspense } from 'react'
import { connection } from 'next/server'
import { cookies } from 'next/headers'
import { cacheLife, cacheTag } from 'next/cache'
 
// 静态产品数据 - 在构建时预渲染
async function getProduct(id: string) {
  'use cache'
  cacheTag(`product-${id}`)
 
  // 这在构建时缓存并在所有用户之间共享
  return db.products.find({ where: { id } })
}
 
// 共享价格数据 - 在运行时在远程处理器中缓存
async function getProductPrice(id: string) {
  'use cache: remote'
  cacheTag(`product-price-${id}`)
  cacheLife({ expire: 300 }) // 5 分钟
 
  // 这在运行时缓存并在所有用户之间共享
  return db.products.getPrice({ where: { id } })
}
 
// 用户特定推荐 - 每用户私有缓存
async function getRecommendations(productId: string) {
  'use cache: private'
  cacheLife({ expire: 60 }) // 1 分钟
 
  const sessionId = (await cookies()).get('session-id')?.value
 
  // 这按用户缓存,永远不会共享
  return db.recommendations.findMany({
    where: { productId, sessionId },
  })
}
 
export default async function ProductPage({ params }) {
  const { id } = await params
 
  // 静态产品数据
  const product = await getProduct(id)
 
  return (
    <div>
      <ProductDetails product={product} />
 
      {/* 动态共享价格 */}
      <Suspense fallback={<PriceSkeleton />}>
        <ProductPriceComponent productId={id} />
      </Suspense>
 
      {/* 动态个性化推荐 */}
      <Suspense fallback={<RecommendationsSkeleton />}>
        <ProductRecommendations productId={id} />
      </Suspense>
    </div>
  )
}
 
function ProductDetails({ product }) {
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </div>
  )
}
 
async function ProductPriceComponent({ productId }) {
  // 使此组件变为动态
  await connection()
 
  const price = await getProductPrice(productId)
  return <div>价格:${price}</div>
}
 
async function ProductRecommendations({ productId }) {
  const recommendations = await getRecommendations(productId)
  return <RecommendationsList items={recommendations} />
}
 
function PriceSkeleton() {
  return <div>加载价格中...</div>
}
 
function RecommendationsSkeleton() {
  return <div>加载推荐中...</div>
}
 
function RecommendationsList({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
}

值得注意的是

  • 远程缓存存储在服务器端缓存处理器中,并在所有用户之间共享
  • 远程缓存在常规 use cache 会失败的动态上下文中工作
  • 使用 cacheTag()revalidateTag() 按需使远程缓存失效
  • 使用 cacheLife() 配置缓存过期
  • 对于用户特定数据,使用 'use cache: private' 而不是 'use cache: remote'
  • 远程缓存通过在服务器端存储计算或获取的数据来减少源负载

平台支持

版本历史

版本变更
v16.0.0'use cache: remote' 作为实验性功能引入。