Skip to content
API ReferenceDirectivesuse cache: remote

use cache: remote

This feature is currently available in the canary channel and subject to change. Try it out by upgrading Next.js, and share your feedback on GitHub.

The 'use cache: remote' directive enables caching of shared data in dynamic contexts where regular use cache would not work, for example after calling await connection(), await cookies() or await headers().

Good to know:

Usage

To use 'use cache: remote', enable the cacheComponents flag in your next.config.ts file:

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

Then add 'use cache: remote' to your function that needs to cache data in a dynamic context.

Basic example

Cache product pricing that needs to be fetched at request time but can be shared across all users. Use cacheLife to set the cache lifetime of the price.

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)
}

Note: Regular use cache will not cache anything when used in a dynamic context (after await connection(), await cookies(), await headers(), etc.). Use 'use cache: remote' to enable runtime caching in these scenarios.

How use cache: remote differs from use cache and use cache: private

Next.js provides three caching directives, each designed for different use cases:

Featureuse cache'use cache: remote''use cache: private'
Works in dynamic contextNo (requires static context)Yes (designed for dynamic contexts)Yes
Access to await cookies()NoNoYes
Access to await headers()NoNoYes
After await connection()No (won't cache)NoNo
Stored in cache handlerYes (server-side)Yes (server-side)No (client-side only)
Cache scopeGlobal (shared)Global (shared)Per-user (isolated)
Supports runtime prefetchingN/A (pre-rendered at build)NoYes (when configured)
Use caseStatic, shared content (build-time)Dynamic, shared content in runtime contexts (per-request)Personalized, user-specific content

Note: While you can't call await cookies() or await headers() inside 'use cache: remote', you can read the values before calling a function that is wrapped by 'use cache: remote' and the arguments will be included in the cache key. Note that this is not recommended as it will dramatically increase the cache size and reduce the cache hit rate.

When to use each directive

Choose the right caching directive based on your use case:

Use use cache when:

  • Content can be prerendered at build time
  • Content is shared across all users
  • Content doesn't depend on request-specific data

Use 'use cache: remote' when:

  • You need caching within dynamic context
  • Content is shared across users but must be rendered per-request (after await connection())
  • You want to cache expensive operations in a server-side cache handler

Use 'use cache: private' when:

  • Content is personalized per-user (depends on cookies, headers)
  • You need runtime prefetching of user-specific content
  • Content should never be shared between users

How it works

The 'use cache: remote' directive enables runtime caching of shared data in dynamic contexts by storing results in server-side cache handlers rather than prerendering at build time.

Dynamic context detection

When Next.js encounters certain APIs like connection(), cookies(), or headers(), the context becomes "dynamic". In a dynamic context:

  1. Regular use cache stops working - it won't cache anything
  2. 'use cache: remote' continues to work - it is cached by a remote cache handler.
  3. Results are stored server-side in a key-value store configured for your deployment
  4. Cached data is shared across requests - reducing database load and origin requests

Good to know: Without 'use cache: remote', functions in dynamic contexts would execute on every request, potentially creating performance bottlenecks. Remote caching eliminates this issue by storing results in server-side cache handlers.

Storage behavior

Remote caches are persisted using server-side cache handlers, which may include:

  • Distributed key-value stores (in-memory or persistent storage solutions)
  • File system or in-memory storage (often used in development or for custom deployments)
  • Environment-specific caches (provided by your hosting infrastructure)
  • Custom or configured cache handlers (depending on your application's setup)

This means:

  1. Cached data is shared across all users and requests
  2. Cache entries persist beyond a single session
  3. Cache invalidation works via cacheTag and revalidateTag
  4. Cache expiration is controlled by cacheLife configuration

Dynamic context example

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()
}

Request APIs and remote caches

While 'use cache: remote' technically allows access to request-specific data by calling API's like cookies() and headers() before calling a function that is wrapped by 'use cache: remote', it's generally not recommended to use them together:

APIAllowed in use cacheAllowed in 'use cache: remote'Recommended
cookies()NoNoUse 'use cache: private' instead
headers()NoNoUse 'use cache: private' instead
connection()NoNoNo - these cannot ever be cached
searchParamsNoNoUse 'use cache: private' instead

Important: If you need to cache based on cookies, headers, or search params, use 'use cache: private' instead. Remote caches are shared across all users, so caching user-specific data in them can lead to incorrect results being served to different users.

Nesting rules

Remote caches have specific nesting rules:

  • Remote caches can be nested inside other remote caches ('use cache: remote')
  • Remote caches can be nested inside regular caches ('use cache')
  • Remote caches cannot be nested inside private caches ('use cache: private')
  • Private caches cannot be nested inside remote caches
// 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()
}

Examples

The following examples demonstrate common patterns for using 'use cache: remote'. For details about cacheLife parameters (stale, revalidate, expire), see the cacheLife API reference.

Per-request database queries

Cache expensive database queries that are accessed in dynamic contexts, reducing load on your database:

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 responses in streaming contexts

Cache API responses that are fetched during streaming or after dynamic operations:

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()
}

Computed data after dynamic checks

Cache expensive computations that occur after dynamic security or feature checks:

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),
  }
}

Mixed caching strategies

Combine static, remote, and private caching for optimal performance:

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>
  )
}

Good to know:

  • Remote caches are stored in server-side cache handlers and shared across all users
  • Remote caches work in dynamic contexts where regular use cache would fail
  • Use cacheTag() and revalidateTag() to invalidate remote caches on-demand
  • Use cacheLife() to configure cache expiration
  • 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

Deployment OptionSupported
Node.js serverYes
Docker containerYes
Static exportNo
AdaptersYes

Version History

VersionChanges
v16.0.0'use cache: remote' introduced as an experimental feature.