use cache: remote
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:
- Results are stored in server-side cache handlers and shared across all users.
- For user-specific data that depends on
await cookies()
orawait headers()
, use'use cache: private'
instead.
Usage
To use 'use cache: remote'
, enable the cacheComponents
flag in your next.config.ts
file:
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.
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 (afterawait 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:
Feature | use cache | 'use cache: remote' | 'use cache: private' |
---|---|---|---|
Works in dynamic context | No (requires static context) | Yes (designed for dynamic contexts) | Yes |
Access to await cookies() | No | No | Yes |
Access to await headers() | No | No | Yes |
After await connection() | No (won't cache) | No | No |
Stored in cache handler | Yes (server-side) | Yes (server-side) | No (client-side only) |
Cache scope | Global (shared) | Global (shared) | Per-user (isolated) |
Supports runtime prefetching | N/A (pre-rendered at build) | No | Yes (when configured) |
Use case | Static, shared content (build-time) | Dynamic, shared content in runtime contexts (per-request) | Personalized, user-specific content |
Note: While you can't call
await cookies()
orawait 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:
- Regular
use cache
stops working - it won't cache anything 'use cache: remote'
continues to work - it is cached by a remote cache handler.- Results are stored server-side in a key-value store configured for your deployment
- 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:
- Cached data is shared across all users and requests
- Cache entries persist beyond a single session
- Cache invalidation works via
cacheTag
andrevalidateTag
- 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:
API | Allowed in use cache | Allowed in 'use cache: remote' | Recommended |
---|---|---|---|
cookies() | No | No | Use 'use cache: private' instead |
headers() | No | No | Use 'use cache: private' instead |
connection() | No | No | No - these cannot ever be cached |
searchParams | No | No | Use '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:
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:
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:
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:
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()
andrevalidateTag()
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 Option | Supported |
---|---|
Node.js server | Yes |
Docker container | Yes |
Static export | No |
Adapters | Yes |
Version History
Version | Changes |
---|---|
v16.0.0 | 'use cache: remote' introduced as an experimental feature. |
Related
use cache
use cache: private
cacheComponents
cacheLife
cacheTag
connection
Was this helpful?