主题
'使用缓存:remote'
¥'use cache: remote'
虽然 use cache 指令足以满足大多数应用的需求,但你可能偶尔会注意到缓存操作的重复运行频率高于预期,或者你的上游服务(CMS、数据库、外部 API)的访问量高于预期。出现这种情况可能是因为内存缓存存在固有的局限性:
¥While the use cache directive is sufficient for most application needs, you might occasionally notice that cached operations are re-running more often than expected, or that your upstream services (CMS, databases, external APIs) are getting more hits than you'd expect. This can happen because in-memory caching has inherent limitations:
缓存条目被清除以腾出空间存放新条目
¥Cache entries being evicted to make room for new ones
部署环境中的内存限制。
¥Memory constraints in your deployment environment
缓存无法在请求或服务器重启后保留
¥Cache not persisting across requests or server restarts
请注意,use cache 除了服务器端缓存之外,还有其他值:它会告知 Next.js 哪些内容可以预取,并定义客户端导航的过期时间。
¥Note that use cache still provides value beyond server-side caching: it informs Next.js what can be prefetched and defines stale times for client-side navigation.
'use cache: remote' 指令允许你以声明方式指定缓存的输出应存储在远程缓存中,而不是内存中。虽然这种方法可以为特定操作提供更持久的缓存,但也存在一些权衡:缓存查找期间会增加基础设施成本和网络延迟。
¥The 'use cache: remote' directive lets you declaratively specify that a cached output should be stored in a remote cache instead of in-memory. While this gives you more durable caching for specific operations, it comes with tradeoffs: infrastructure cost and network latency during cache lookups.
用法
¥Usage
要使用 'use cache: remote',请在 next.config.ts 文件中启用 cacheComponents 标志:
¥To use 'use cache: remote', enable the cacheComponents flag in your next.config.ts file:
ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig然后将 'use cache: remote' 添加到你确定需要远程缓存的函数或组件中。处理程序实现通过 cacheHandlers 进行配置,但托管服务提供商通常会自动提供此配置。如果你是自托管,请参阅 cacheHandlers 配置参考文档来设置缓存存储。
¥Then add 'use cache: remote' to the functions or components where you've determined remote caching is justified. The handler implementation is configured via cacheHandlers, though hosting providers should typically provide this automatically. If you're self-hosting, see the cacheHandlers configuration reference to set up your cache storage.
何时避免使用远程缓存
¥When to avoid remote caching
如果你已经有一个服务器端缓存键值存储来封装你的数据层,那么
use cache可能足以将数据包含在静态 shell 中,而无需添加额外的缓存层。¥If you already have a server-side cache key-value store wrapping your data layer,
use cachemay be sufficient to include data in the static shell without adding another caching layer如果由于距离近或本地访问,操作速度已经很快(< 50 毫秒),则远程缓存查找可能不会提升性能。
¥If operations are already fast (< 50ms) due to proximity or local access, the remote cache lookup might not improve performance
如果缓存键在每个请求中大多具有唯一值(例如搜索过滤器、价格范围、用户特定参数),则缓存利用率将接近于零。
¥If cache keys have mostly unique values per request (search filters, price ranges, user-specific parameters), cache utilization will be near-zero
如果数据频繁变化(几秒到几分钟),缓存命中会很快失效,导致频繁的缓存未命中,需要等待上游重新验证。
¥If data changes frequently (seconds to minutes), cache hits will quickly go stale, leading to frequent misses and waiting for upstream revalidation
何时使用远程缓存
¥When remote caching makes sense
当内容延迟到请求时(在静态 shell 之外),远程缓存的值最大。这通常发生在组件访问请求值(例如 cookies()、headers() 或 searchParams)并将其置于 Suspense 边界内时。在此上下文中:
¥Remote caching provides the most value when content is deferred to request time (outside the static shell). This typically happens when a component accesses request values like cookies(), headers(), or searchParams, placing it inside a Suspense boundary. In this context:
每个请求都会执行组件并查找缓存。
¥Each request executes the component and looks up the cache
在无服务器环境中,每个实例都有自己的临时内存,缓存命中率较低。
¥In serverless environments, each instance has its own ephemeral memory with low cache hit rates
远程缓存提供跨所有实例的共享缓存,从而提高命中率并降低后端负载。
¥Remote caching provides a shared cache across all instances, improving hit rates and reducing backend load
'use cache: remote' 的典型应用场景:
¥Compelling scenarios for 'use cache: remote':
限速 API:你的上游服务有速率限制或请求配额,你可能会超出限制。
¥Rate-limited APIs: Your upstream service has rate limits or request quotas that you risk hitting
保护慢速后端:你的数据库或 API 在高流量下成为瓶颈
¥Protecting slow backends: Your database or API becomes a bottleneck under high traffic
高开销操作:重复运行成本较高的数据库查询或计算
¥Expensive operations: Database queries or computations that are costly to run repeatedly
不稳定或不可靠的服务:偶尔出现故障或可用性问题的外部服务
¥Flaky or unreliable services: External services that occasionally fail or have availability issues
在这些情况下,远程缓存的成本和延迟是值得的,因为它可以避免更糟糕的结果(速率限制错误、后端过载、高额计算费用或用户体验下降)。
¥In these cases, the cost and latency of remote caching is justified by avoiding worse outcomes (rate limit errors, backend overload, high compute bills, or degraded user experience).
对于静态 shell 内容,use cache 通常足够。但是,如果你的静态页面共享来自上游的数据,而上游无法处理并发的重新验证请求(例如速率限制的 CMS),则 use cache: remote 会充当该上游前端的共享缓存层。这与在数据库前面放置键值存储的模式相同,但它是在代码中声明的,而不是在基础设施中配置的。
¥For static shell content, use cache is usually sufficient. However, if your static pages share data from an upstream that can't handle concurrent revalidation requests (like a rate-limited CMS), use cache: remote acts as a shared cache layer in front of that upstream. This is the same pattern as putting a key-value store in front of a database, but declared in your code rather than configured in infrastructure.
use cache: remote 与 use cache 和 use cache: private 的区别
¥How use cache: remote differs from use cache and use cache: private
Next.js 提供了三种缓存指令,每种指令都针对不同的使用场景:
¥Next.js provides three caching directives, each designed for different use cases:
| 功能 | use cache | 'use cache: remote' | 'use cache: private' |
|---|---|---|---|
| 服务器端缓存 | 内存或缓存处理程序 | 远程缓存处理程序 | 无 |
| 缓存范围 | 所有用户共享 | 所有用户共享 | 客户端(浏览器) |
| 可直接访问 cookie/headers | 否(必须作为参数传递) | 否(必须作为参数传递) | 是 |
| 服务器缓存利用率 | 在静态 shell 之外可能较低。 | 高(跨实例共享) | N/A |
| 额外费用 | 无 | 基础设施(存储、网络) | 无 |
| 延迟影响 | 无 | 缓存处理程序查找 | 无 |
使用运行时数据进行缓存
¥Caching with runtime data
use cache 和 'use cache: remote' 指令都无法直接访问运行时数据(例如 cookie 或搜索参数),因为在计算缓存时这些数据不可用。但是,你可以从这些 API 中提取值,并将其作为参数传递给缓存函数。请参阅 处理运行时数据 以了解此模式。
¥Both use cache and 'use cache: remote' directives can't access runtime data like cookies or search params directly, since this data isn't available when computing the cache. However, you can extract values from these APIs and pass them as arguments to cached functions. See with runtime data for this pattern.
通常情况下,尤其对于 'use cache: remote' 而言,请谨慎选择缓存键中包含的值。每个唯一值都会创建一个单独的缓存条目,从而降低缓存利用率。考虑以下带有搜索过滤器的示例:
¥In general, but most importantly for 'use cache: remote', be thoughtful about which values you include in cache keys. Each unique value creates a separate cache entry, reducing cache utilization. Consider this example with search filters:
tsx
import { Suspense } from 'react'
export default async function ProductsPage({
params,
searchParams,
}: {
params: Promise<{ category: string }>
searchParams: Promise<{ minPrice?: string }>
}) {
return (
<Suspense fallback={<div>Loading...</div>}>
<ProductList params={params} searchParams={searchParams} />
</Suspense>
)
}
async function ProductList({
params,
searchParams,
}: {
params: Promise<{ category: string }>
searchParams: Promise<{ minPrice?: string }>
}) {
const { category } = await params
const { minPrice } = await searchParams
// Cache only on category (few unique values)
// Don't include price filter (many unique values)
const products = await getProductsByCategory(category)
// Filter price in memory instead of creating cache entries
// for every price value
const filtered = minPrice
? products.filter((p) => p.price >= parseFloat(minPrice))
: products
return <div>{/* render filtered products */}</div>
}
async function getProductsByCategory(category: string) {
'use cache: remote'
// Only category is part of the cache key
// Much better utilization than caching every price filter value
return db.products.findByCategory(category)
}在本例中,远程处理程序会为每个缓存条目(一个类别中的所有产品)存储更多数据,以提高缓存命中率。当缓存未命中(访问后端)的成本超过存储较大条目的成本时,此功能是值得的。
¥In this example, the remote handler stores more data per cache entry (all products in a category) to achieve better cache hit rates. This is worth it when the cost of cache misses (hitting your backend) outweighs the storage cost of larger entries.
同样的原则也适用于用户特定数据。不要直接缓存每个用户的数据,而是使用用户偏好来确定要缓存哪些共享数据。
¥The same principle applies to user-specific data. Rather than caching per-user data directly, use user preferences to determine what shared data to cache.
例如,如果用户在其会话中设置了语言偏好,则提取该偏好并使用它来缓存共享内容:
¥For example, if users have a language preference in their session, extract that preference and use it to cache shared content:
不要远程缓存
getUserProfile(sessionID)(这会为每个用户创建一个条目)¥Instead of remote caching
getUserProfile(sessionID), which creates one entry per user使用远程缓存
getCMSContent(language)为每种语言创建一个条目¥Remote cache
getCMSContent(language)to create one entry per language
tsx
import { cookies } from 'next/headers'
import { cacheLife } from 'next/cache'
export async function WelcomeMessage() {
// Extract the language preference (not unique per user)
const language = (await cookies()).get('language')?.value || 'en'
// Cache based on language (few unique values: en, es, fr, de, etc.)
// All users who prefer 'en' share the same cache entry
const content = await getCMSContent(language)
return <div>{content.welcomeMessage}</div>
}
async function getCMSContent(language: string) {
'use cache: remote'
cacheLife({ expire: 3600 })
// Creates ~10-50 cache entries (one per language)
// instead of thousands (one per user)
return cms.getHomeContent(language)
}这样,所有偏好相同语言的用户共享一个缓存条目,从而提高缓存利用率并降低 CMS 的负载。
¥This way all users who prefer the same language share a cache entry, improving cache utilization and reducing load on your CMS.
两个示例中的模式相同:查找唯一值较少的维度(例如类别与价格、语言与用户 ID),缓存该维度的数据,并在内存中筛选或选择其余数据。
¥The pattern is the same in both examples: find the dimension with fewer unique values (category vs. price, language vs. user ID), cache on that dimension, and filter or select the rest in memory.
如果 getUserProfile 使用的服务无法随着前端负载的增加而扩展,你仍然可以使用 use cache 指令,并配合简短的 cacheLife 指令进行内存缓存。但是,对于大多数用户数据,你可能希望直接从源头获取(如上述指南中所述,源头可能已经封装在键值存储中)。
¥If the service used by getUserProfile cannot scale with your frontend load, you may still be able to use the use cache directive with a short cacheLife for in-memory caching. However, for most user data, you likely want to fetch directly from the source (which might already be wrapped in a key/value store as mentioned in the guidelines above).
仅当你有合规性要求或无法重构代码以将运行时数据作为参数传递时才使用 'use cache: private'。
¥Only use 'use cache: private' if you have compliance requirements or can't refactor to pass runtime data as arguments.
嵌套规则
¥Nesting rules
远程缓存具有特定的嵌套规则:
¥Remote caches have specific nesting rules:
远程缓存可以嵌套在其他远程缓存 (
'use cache: remote') 中¥Remote caches can be nested inside other remote caches (
'use cache: remote')远程缓存可以嵌套在常规缓存 (
'use cache') 中¥Remote caches can be nested inside regular caches (
'use cache')远程缓存不能嵌套在私有缓存中(
'use cache: private')¥Remote caches cannot be nested inside private caches (
'use cache: private')私有缓存不能嵌套在远程缓存中。
¥Private caches cannot be nested inside remote caches
tsx
// 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'
// The inner remote cache will work when deferred to request time
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()
}示例
¥Examples
以下示例演示了使用 'use cache: remote' 的常见模式。有关 cacheLife 参数(stale、revalidate、expire)的详细信息,请参阅 cacheLife API 参考 文档。
¥The following examples demonstrate common patterns for using 'use cache: remote'. For details about cacheLife parameters (stale, revalidate, expire), see the cacheLife API reference.
处理用户偏好设置
¥With user preferences
缓存产品定价基于用户的货币偏好。由于货币信息存储在 cookie 中,因此此组件会在请求时渲染。远程缓存在此处非常有用,因为所有使用相同货币的用户共享缓存的价格,并且在无服务器环境中,所有实例共享同一个远程缓存。
¥Cache product pricing based on the user's currency preference. Since the currency is stored in a cookie, this component renders at request time. Remote caching is valuable here because all users with the same currency share the cached price, and in serverless environments, all instances share the same remote cache.
tsx
import { Suspense } from 'react'
import { cookies } from 'next/headers'
import { cacheTag, cacheLife } from 'next/cache'
export async function generateStaticParams() {
return [{ id: '1' }, { id: '2' }, { id: '3' }]
}
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 }) {
// Reading cookies defers this component to request time
const currency = (await cookies()).get('currency')?.value ?? 'USD'
// Cache the price per product and currency combination
// All users with the same currency share this cache entry
const price = await getProductPrice(productId, currency)
return (
<div>
Price: {price} {currency}
</div>
)
}
async function getProductPrice(productId: string, currency: string) {
'use cache: remote'
cacheTag(`product-price-${productId}`)
cacheLife({ expire: 3600 }) // 1 hour
// Cached per (productId, currency) - few currencies means high cache utilization
return db.products.getPrice(productId, currency)
}降低数据库负载
¥Reducing database load
缓存耗时的数据库查询,从而减轻数据库负载。在本例中,我们不访问 cookies()、headers() 或 searchParams。如果我们需要不在静态 shell 中包含这些统计信息,则可以使用 connection() 显式地延迟到请求时执行:
¥Cache expensive database queries, reducing load on your database. In this example, we don't access cookies(), headers(), or searchParams. If we had a requirement to not include these stats in the static shell, we could use connection() to explicitly defer to request time:
tsx
import { Suspense } from 'react'
import { connection } from 'next/server'
import { cacheLife, cacheTag } from 'next/cache'
export default function DashboardPage() {
return (
<Suspense fallback={<div>Loading stats...</div>}>
<DashboardStats />
</Suspense>
)
}
async function DashboardStats() {
// Defer to request time
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
}通过此设置,无论有多少用户访问仪表板,你的上游数据库每分钟最多只会收到一个请求。
¥With this setup, your upstream database sees at most one request per minute, regardless of how many users visit the dashboard.
流式上下文中的 API 响应
¥API responses in streaming contexts
缓存流式传输或动态操作后获取的 API 响应:
¥Cache API responses that are fetched during streaming or after dynamic operations:
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() {
// Defer to request time
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()
}动态检查后的计算数据
¥Computed data after dynamic checks
缓存动态安全或功能检查后发生的耗时计算:
¥Cache expensive computations that occur after dynamic security or feature checks:
tsx
import { connection } from 'next/server'
import { cacheLife } from 'next/cache'
export default async function ReportsPage() {
// Defer to request time (for 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),
}
}混合缓存策略
¥Mixed caching strategies
结合静态缓存、远程缓存和私有缓存以获得最佳性能:
¥Combine static, remote, and private caching for optimal performance:
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 }) {
// Defer to request time
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>
)
}需要了解:
¥Good to know:
远程缓存存储在服务器端缓存处理程序中,并供所有用户共享
¥Remote caches are stored in server-side cache handlers and shared across all users
'use cache: remote'在静态 shell 之外工作,而use cache可能无法提供服务器端缓存命中。¥
'use cache: remote'works outside the static shell whereuse cachemay not provide server-side cache hits使用
cacheTag()和revalidateTag()按需使远程缓存失效¥Use
cacheTag()andrevalidateTag()to invalidate remote caches on-demand使用
cacheLife()配置缓存过期时间。¥Use
cacheLife()to configure cache expiration对于用户特定数据,请使用
'use cache: private'而不是'use cache: remote'¥For user-specific data, use
'use cache: private'instead of'use cache: remote'远程缓存通过在服务器端存储计算或获取的数据来降低源服务器的负载。
¥Remote caches reduce origin load by storing computed or fetched data server-side
平台支持
¥Platform Support
| 部署选项 | 支持 |
|---|---|
| Node.js 服务器 | 是 |
| Docker 容器 | 是 |
| 静态导出 | 否 |
| 适配器 | 是 |
版本历史
¥Version History
| 版本 | 更改 |
|---|---|
v16.0.0 | "use cache: remote" 通过缓存组件功能启用。 |