`use cache: remote` Explained: Shared Cache Across Instances

use cache: remote stores cached output in a durable handler shared across server instances. Learn when it pays off and how to keep hit rates high.

8 min read

The use cache: remote directive stores cached output in a durable cache handler instead of in-memory storage, so every server instance shares one cache. It requires Cache Components and is useful when in-memory entries keep getting evicted or re-run on each serverless request.

You add it the same way as regular use cache:

typescripttypescript
// app/lib/products.ts
import { cacheLife, cacheTag } from 'next/cache'
 
export async function getProductPrice(productId: string, currency: string) {
  'use cache: remote'
  cacheTag(`product-price-${productId}`)
  cacheLife({ expire: 3600 })
  const res = await fetch(`https://api.example.com/prices/${productId}?currency=${currency}`)
  return res.json()
}

The result now lives in a remote handler rather than the current instance's memory. In a serverless environment this means many instances can share one entry, so a price fetched once is not fetched again by the next instance. The storage backend is configured through cacheHandlers, though hosting providers usually set it up for you.

How it differs from the other directives

Featureuse cacheuse cache: remoteuse cache: private
Server-side cachingIn-memory or cache handlerRemote cache handlerNone
ScopeShared across usersShared across usersPer browser
Reads cookies and headersNoNoYes
Extra costsNoneStorage and networkNone

The remote variant adds a network roundtrip for every lookup, so it only wins when that cost is cheaper than the backend work it skips.

When remote caching pays off

  • Your upstream API has rate limits or request quotas you risk hitting.
  • Your database or CMS becomes a bottleneck under high traffic.
  • The operation is expensive to run repeatedly, such as an aggregate report.
  • The upstream service is flaky and you want to absorb failures.
  • Serverless instances have ephemeral memory, so most in-memory lookups miss.

For static shell content, regular use cache is usually enough. Remote caching earns its cost when content is deferred to request time and many instances would otherwise miss the in-memory cache.

Cache key considerations

Hit rate depends on keys repeating. Cache on a dimension with few values, then filter in memory for the rest.

App.tsxApp.tsx
// app/components/welcome-message.tsx
import { cookies } from 'next/headers'
import { cacheLife } from 'next/cache'
 
export async function WelcomeMessage() {
  const language = (await cookies()).get('language')?.value || 'en'
  const content = await getCMSContent(language)
  return <p>{content.welcomeMessage}</p>
}
 
async function getCMSContent(language: string) {
  'use cache: remote'
  cacheLife({ expire: 3600 })
  const res = await fetch(`https://cms.example.com/home?language=${language}`)
  return res.json()
}

The remote entry is keyed by language, not by user. A handful of languages produces a handful of shared entries, while keying by user ID would produce thousands of entries that almost never repeat.

WelcomeMessage itself reads cookies, so it renders at request time and the route must place it inside a Suspense boundary. That is exactly the situation remote caching is built for, because the in-memory cache rarely helps once a component is deferred past the static shell.

The same idea applies to price filters: cache per category and filter by price in memory rather than creating an entry per price value. Read cookies outside the cached scope and pass the extracted value in as an argument.

Nesting rules

The remote directive follows specific nesting rules.

  • Remote inside remote is allowed.
  • Remote inside regular use cache is allowed.
  • Remote inside private is not allowed.
  • Private inside remote is not allowed.

The rule exists because private results never leave the browser, so a shared entry cannot be built inside one. Keep the combinations in mind when a cached component calls another cached helper.

Persistence across deploys

Remote entries do not survive a new deploy. The cache key includes the build ID or deploymentId, so a fresh build produces fresh keys and the old entries become unreachable.

Between builds, a function's identity hash or the shape of its return value can change, so reusing old entries would risk serving stale or malformed data. For data that must persist across deploys, use the fetch cache or unstable_cache instead.

Common mistakes

  • Keying on user-specific values, which destroys the shared hit rate.
  • Using remote for static shell content where regular use cache already works.
  • Expecting remote entries to carry over between deploys.

For the per-user browser-only case, see the private variant. For the directive's scopes, see the use cache directive. For invalidating tagged entries, see cacheTag explained.

Rune AI

Rune AI

Key Insights

  • use cache: remote stores output in a durable shared cache handler.
  • It trades infrastructure cost and lookup latency for higher hit rates.
  • Cache keys should repeat, so cache on dimensions with few values.
  • Remote entries do not persist across deploys.
  • Remote can nest inside remote or use cache, but not private.
RunePowered by Rune AI

Frequently Asked Questions

Do remote cache entries survive a new deploy?

No. The cache key includes the build ID or deploymentId, so a new deploy produces new keys and old entries become unreachable.

When should I avoid use cache: remote?

When operations are already fast, when cache keys are mostly unique per request, or when data changes every few seconds and hits go stale quickly.

Conclusion

use cache: remote moves cached output into a durable handler shared across instances. It pays off at high traffic with slow or rate-limited backends, but only when cache keys repeat. Keep keys on low-cardinality dimensions and prefer regular use cache for static shell content.