跳到内容
API 参考指令use cache: remote

use cache: remote

此功能目前在 Canary 频道中可用,并可能发生变化。您可以通过升级 Next.js 来试用它,并在 GitHub 上分享您的反馈。

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

须知

用法

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

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

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

基本示例

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

app/product/[id]/page.tsx
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>Loading price...</div>}>
        <ProductPrice productId={id} />
      </Suspense>
    </div>
  )
}
 
function ProductDetails({ id }: { id: string }) {
  return <div>Product: {id}</div>
}
 
async function ProductPrice({ productId }: { productId: string }) {
  // Calling connection() makes this component dynamic, preventing
  // it from being included in the static shell. This ensures the price
  // is always fetched at request time.
  await connection()
 
  // Now we can cache the price in a remote cache handler.
  // Regular 'use cache' would NOT work here because we're in a dynamic context.
  const price = await getProductPrice(productId)
 
  return <div>Price: ${price}</div>
}
 
async function getProductPrice(productId: string) {
  'use cache: remote'
  cacheTag(`product-price-${productId}`)
  cacheLife({ expire: 3600 }) // 1 hour
 
  // This database query is cached and shared across all users
  return db.products.getPrice(productId)
}

注意:在动态上下文(例如在 await connection()await cookies()await headers() 等之后)中使用时,常规的 use cache 将不会缓存任何内容。请使用 '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'

  • 内容按用户个性化(依赖于 cookie、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() {
  // Calling connection() makes the context dynamic
  await connection()
 
  // Without any caching directive, this runs on every request
  const stats = await getStats()
 
  // With 'use cache: remote', this is cached in the remote handler
  const analytics = await getAnalytics()
 
  return (
    <div>
      <Stats data={stats} />
      <Analytics data={analytics} />
    </div>
  )
}
 
async function getAnalytics() {
  'use cache: remote'
  cacheLife({ expire: 300 }) // 5 minutes
 
  // This expensive operation is cached and shared across all requests
  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'

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

嵌套规则

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

  • 远程缓存**可以**嵌套在其他远程缓存中('use cache: remote'
  • 远程缓存**可以**嵌套在常规缓存中('use cache'
  • 远程缓存**不能**嵌套在私有缓存中('use cache: private'
  • 私有缓存**不能**嵌套在远程缓存中
// VALID: Remote inside remote
async function outerRemote() {
  'use cache: remote'
  const result = await innerRemote()
  return result
}
 
async function innerRemote() {
  'use cache: remote'
  return getData()
}
 
// VALID: Remote inside regular cache
async function outerCache() {
  'use cache'
  // If this is in a dynamic context, the inner remote cache will work
  const result = await innerRemote()
  return result
}
 
async function innerRemote() {
  'use cache: remote'
  return getData()
}
 
// INVALID: Remote inside private
async function outerPrivate() {
  'use cache: private'
  const result = await innerRemote() // Error!
  return result
}
 
async function innerRemote() {
  'use cache: remote'
  return getData()
}
 
// INVALID: Private inside remote
async function outerRemote() {
  'use cache: remote'
  const result = await innerPrivate() // Error!
  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() {
  // Make context dynamic
  await connection()
 
  const stats = await getGlobalStats()
 
  return <StatsDisplay stats={stats} />
}
 
async function getGlobalStats() {
  'use cache: remote'
  cacheTag('global-stats')
  cacheLife({ expire: 60 }) // 1 minute
 
  // This expensive database query is cached and shared across all users,
  // reducing load on your database
  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() {
  // Dynamic context
  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 minutes
 
  // This API call is cached, reducing requests to your external service
  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() {
  // Dynamic security check
  await connection()
 
  const report = await generateReport()
 
  return <ReportViewer report={report} />
}
 
async function generateReport() {
  'use cache: remote'
  cacheLife({ expire: 3600 }) // 1 hour
 
  // This expensive computation is cached and shared across all authorized users,
  // avoiding repeated calculations
  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'
 
// Static product data - prerendered at build time
async function getProduct(id: string) {
  'use cache'
  cacheTag(`product-${id}`)
 
  // This is cached at build time and shared across all users
  return db.products.find({ where: { id } })
}
 
// Shared pricing data - cached at runtime in remote handler
async function getProductPrice(id: string) {
  'use cache: remote'
  cacheTag(`product-price-${id}`)
  cacheLife({ expire: 300 }) // 5 minutes
 
  // This is cached at runtime and shared across all users
  return db.products.getPrice({ where: { id } })
}
 
// User-specific recommendations - private cache per user
async function getRecommendations(productId: string) {
  'use cache: private'
  cacheLife({ expire: 60 }) // 1 minute
 
  const sessionId = (await cookies()).get('session-id')?.value
 
  // This is cached per-user and never shared
  return db.recommendations.findMany({
    where: { productId, sessionId },
  })
}
 
export default async function ProductPage({ params }) {
  const { id } = await params
 
  // Static product data
  const product = await getProduct(id)
 
  return (
    <div>
      <ProductDetails product={product} />
 
      {/* Dynamic shared price */}
      <Suspense fallback={<PriceSkeleton />}>
        <ProductPriceComponent productId={id} />
      </Suspense>
 
      {/* Dynamic personalized recommendations */}
      <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 }) {
  // Make this component dynamic
  await connection()
 
  const price = await getProductPrice(productId)
  return <div>Price: ${price}</div>
}
 
async function ProductRecommendations({ productId }) {
  const recommendations = await getRecommendations(productId)
  return <RecommendationsList items={recommendations} />
}
 
function PriceSkeleton() {
  return <div>Loading price...</div>
}
 
function RecommendationsSkeleton() {
  return <div>Loading recommendations...</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' 作为实验性功能推出。