'use cache: remote'

Store a cached result in a remote cache handler so all server instances share it.

Signature
'use cache: remote'
  • Added in Next.js v16.0.0

Return value

Nothing directly; the covered function returns its own value, stored in and served from the configured remote cache handler.

Errors

Reading cookies(), headers() or searchParams inside the scope throws, as with 'use cache'. Nesting a remote cache inside a 'use cache: private' scope, or a private cache inside a remote one, is also an error.

Changelog

  • PHP v16.0.0"use cache: remote" is enabled with the Cache Components feature.

'use cache: remote' moves an entry out of process memory and into a cache handler configured through cacheHandlers — typically supplied by your host, or set up yourself when self-hosting. Every server instance then shares one copy.

Reach for it when

The symptom is a cached operation that re-runs far more often than it should, or an upstream service taking more traffic than you expect. That happens because plain 'use cache' is in-memory: on serverless, each request may hit a fresh instance with an empty cache. The wins are concrete — a rate-limited API, a database that buckles under load, an expensive computation, a flaky third party. It pays off most for content deferred to request time, outside the static shell, where in-memory hit rates are near zero.

What trips people up

  • Every lookup is a network round trip and usually a platform bill. For operations already under ~50ms, or keys nearly unique per request (search filters, price ranges, user IDs), utilization approaches zero and you have paid for nothing.
  • It still cannot read cookies() or headers() — that is 'use cache: private'. Extract the low-cardinality dimension and cache on that.
  • Entries do not survive a deploy: the key includes the build or deploymentId.

Example

Example · typescript
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

  const stats = await db.analytics.aggregate({
    total_users: 'count',
    active_sessions: 'count',
    revenue: 'sum',
  })

  return stats
}

When to use it

  • Protect a rate-limited CMS from concurrent revalidation across many serverless instances.
  • Cap an expensive dashboard aggregate at one database query per minute, fleet-wide.
  • Share a per-currency price lookup between instances so cookie-driven pages still hit cache.

Frequently asked questions

When should I use 'use cache: remote' instead of 'use cache'?

When entries need to persist and be shared across server instances — most often for content deferred to request time in a serverless deployment, where each instance has its own ephemeral memory and hit rates are near zero. It is worth the storage cost and lookup latency for rate-limited APIs, slow backends, expensive operations, and flaky services; for static shell content, plain 'use cache' is usually enough.

How do I configure the remote cache handler?

Through the cacheHandlers option in next.config, alongside cacheComponents: true. Hosting providers typically supply the handler automatically; if you self-host, you point it at your own store such as Redis or a KV database.

Do remote cache entries survive a new deployment?

No. The cache key includes the deploymentId when configured, or the build ID otherwise, so a new build produces new keys and the previous entries are unreachable. This is intentional: a refactor or dependency upgrade can change a cached function's return shape, and reusing old entries would risk serving malformed data.

More examples

Choosing the cache key dimension

Lives at app/components/welcome-message.tsx. Caching getCMSContent(language) rather than getUserProfile(sessionId) is the difference between a few dozen entries with high hit rates and one entry per user with none. Find the dimension with few unique values, cache on it, filter the rest in memory.

Example · typescript
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'

  const content = await getCMSContent(language)

  return <div>{content.welcomeMessage}</div>
}

async function getCMSContent(language: string) {
  'use cache: remote'
  cacheLife({ expire: 3600 })
  // ~10-50 entries (one per language) instead of thousands (one per user)
  return cms.getHomeContent(language)
}

Nesting rules

Remote nests inside remote and inside plain 'use cache', but never crosses with private in either direction. The reason is scope: a private entry belongs to one browser, so a shared remote entry cannot be produced from inside it, or vice versa.

Example · typescript
// VALID: remote inside remote
async function outerRemote() {
  'use cache: remote'
  return innerRemote()
}

// VALID: remote inside a regular cache
async function outerCache() {
  'use cache'
  return innerRemote()
}

async function innerRemote() {
  'use cache: remote'
  return getData()
}

// INVALID: remote inside private
async function outerPrivate() {
  'use cache: private'
  return innerRemote() // Error
}

Invalidating a remote entry

Lives at app/actions.ts. Remote entries carry cacheTag labels like any other, so on-demand invalidation is unchanged — you do not talk to the cache handler directly.

Example · typescript
'use server'

import { revalidateTag } from 'next/cache'

export async function refreshStats() {
  await recomputeAnalytics()

  // Same tag API as every other cache scope
  revalidateTag('global-stats', 'max')
}

Discussion

  • Be the first to comment on this lesson.