Skip to content

use cache

Source URL: https://nextjs.org/docs/app/api-reference/directives/use-cache

The use cache directive allows you to mark a route, React component, or a function as cacheable. It can be used at the top of a file to indicate that all exports in the file should be cached, or inline at the top of function or component to cache the return value.

Good to know:

  • To use cookies or headers, read them outside cached scopes and pass values as arguments. This is the preferred pattern.
  • If the in-memory cache isn’t sufficient for runtime data, 'use cache: remote' allows platforms to provide a dedicated cache handler, though it requires a network roundtrip to check the cache and typically incurs platform fees.
  • For compliance requirements or when you can’t refactor to pass runtime data as arguments to a use cache scope, see 'use cache: private'.

use cache is a Cache Components feature. To enable it, add the cacheComponents option to your next.config.ts file:

import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig
/** @type {import('next').NextConfig} */
const nextConfig = {
cacheComponents: true,
}
module.exports = nextConfig

Then, add use cache at the file, component, or function level:

// File level
'use cache'
export default async function Page() {
// ...
}
// Component level
export async function MyComponent() {
'use cache'
return <></>
}
// Function level
export async function getData() {
'use cache'
const data = await fetch('/api/data')
return data
}

Good to know: When used at file level, all function exports must be async functions.

A cache entry’s key is generated using a serialized version of its inputs, which includes:

  1. Build ID - Unique per build, changing this invalidates all cache entries
  2. Function ID - A secure hash of the function’s location and signature in the codebase
  3. Serializable arguments - Props (for components) or function arguments
  4. HMR refresh hash (development only) - Invalidates cache on hot module replacement

When a cached function references variables from outer scopes, those variables are automatically captured and bound as arguments, making them part of the cache key.

async function Component({ userId }: { userId: string }) {
const getData = async (filter: string) => {
'use cache'
// Cache key includes both userId (from closure) and filter (argument)
return fetch(`/api/users/${userId}/data?filter=${filter}`)
}
return getData('active')
}

In the snippet above, userId is captured from the outer scope and filter is passed as an argument, so both become part of the getData function’s cache key. This means different user and filter combinations will have separate cache entries.

Arguments to cached functions and their return values must be serializable.

For a complete reference, see:

Good to know: Arguments and return values use different serialization systems. Server Component serialization (for arguments) is more restrictive than Client Component serialization (for return values). This means you can return JSX elements but cannot accept them as arguments unless using pass-through patterns.

Arguments:

  • Primitives: string, number, boolean, null, undefined
  • Plain objects: { key: value }
  • Arrays: [1, 2, 3]
  • Dates, Maps, Sets, TypedArrays, ArrayBuffers
  • React elements (as pass-through only)

Return values:

  • Same as arguments, plus JSX elements
  • Class instances
  • Functions (except as pass-through)
  • Symbols, WeakMaps, WeakSets
  • URL instances
// Valid - primitives and plain objects
async function UserCard({
id,
config,
}: {
id: string
config: { theme: string }
}) {
'use cache'
return <div>{id}</div>
}
// Invalid - class instance
async function UserProfile({ user }: { user: UserClass }) {
'use cache'
// Error: Cannot serialize class instance
return <div>{user.name}</div>
}

You can accept non-serializable values as long as you don’t introspect them. This enables composition patterns with children and Server Actions:

async function CachedWrapper({ children }: { children: ReactNode }) {
'use cache'
// Don't read or modify children - just pass it through
return (
<div className="wrapper">
<header>Cached Header</header>
{children}
</div>
)
}
// Usage: children can be dynamic
export default function Page() {
return (
<CachedWrapper>
<DynamicComponent /> {/* Not cached, passed through */}
</CachedWrapper>
)
}

You can also pass Server Actions through cached components:

async function CachedForm({ action }: { action: () => Promise<void> }) {
'use cache'
// Don't call action here - just pass it through
return <form action={action}>{/* ... */}</form>
}

Cached functions execute in an isolated environment. The following constraints ensure cache behavior remains predictable and secure.

Cached functions and components cannot directly access runtime APIs like cookies(), headers(), or searchParams. Instead, read these values outside the cached scope and pass them as arguments.

While use cache is designed primarily to include dynamic data in the static shell, it can also cache data at runtime using in-memory LRU (Least Recently Used) storage.

Runtime cache behavior depends on your hosting environment:

EnvironmentRuntime Caching Behavior
ServerlessCache entries typically don’t persist across requests (each request can be a different instance). Build-time caching works normally.
Self-hostedCache entries persist across requests. Control cache size with cacheMaxMemorySize.

If the default in-memory cache isn’t enough, consider use cache: remote which allows platforms to provide a dedicated cache handler (like Redis or KV database). This helps reduce hits against data sources not scaled to your total traffic, though it comes with costs (storage, network latency, platform fees).

Very rarely, for compliance requirements or when you can’t refactor your code to pass runtime data as arguments to a use cache scope, you might need use cache: private.

React.cache operates in an isolated scope inside use cache boundaries. Values stored via React.cache outside a use cache function are not visible inside it.

This means you cannot use React.cache to pass data into a use cache scope:

import { cache } from 'react'
const store = cache(() => ({ current: null as string | null }))
function Parent() {
const shared = store()
shared.current = 'value from parent'
return <Child />
}
async function Child() {
'use cache'
const shared = store()
// shared.current is null, not 'value from parent'
// use cache has its own isolated React.cache scope
return <div>{shared.current}</div>
}

This isolation ensures cached functions have predictable, self-contained behavior. To pass data into a use cache scope, use function arguments instead.

On the server, cache entries are stored in-memory and respect the revalidate and expire times from your cacheLife configuration. You can customize the cache storage by configuring cacheHandlers in your next.config.js file.

On the client, content from the server cache is stored in the browser’s memory for the duration defined by the stale time. The client router enforces a minimum 30-second stale time, regardless of configuration.

The x-nextjs-stale-time response header communicates cache lifetime from server to client, ensuring coordinated behavior.

By default, use cache uses the default profile with these settings:

  • stale: 5 minutes (client-side)
  • revalidate: 15 minutes (server-side)
  • expire: Never expires by time
async function getData() {
'use cache'
// Implicitly uses default profile
return fetch('/api/data')
}

Use the cacheLife function to customize cache duration:

import { cacheLife } from 'next/cache'
async function getData() {
'use cache'
cacheLife('hours') // Use built-in 'hours' profile
return fetch('/api/data')
}

Use cacheTag, updateTag, or revalidateTag for on-demand cache invalidation:

import { cacheTag } from 'next/cache'
async function getProducts() {
'use cache'
cacheTag('products')
return fetch('/api/products')
}
'use server'
import { updateTag } from 'next/cache'
export async function updateProduct() {
await db.products.update(...)
updateTag('products') // Invalidates all 'products' caches
}

Both cacheLife and cacheTag integrate across client and server caching layers, meaning you configure your caching semantics in one place and they apply everywhere.

To pre-render an entire route, add use cache to the top of both the layout and page files. Each of these segments are treated as separate entry points in your application, and will be cached independently.

'use cache'
export default async function Layout({ children }: { children: ReactNode }) {
return <div>{children}</div>
}
'use cache'
export default async function Layout({ children }) {
return <div>{children}</div>
}

Any components imported and nested in page file are part of the cache output associated with the page.

'use cache'
async function Users() {
const users = await fetch('/api/users')
// loop through users
}
export default async function Page() {
return (
<main>
<Users />
</main>
)
}
'use cache'
async function Users() {
const users = await fetch('/api/users')
// loop through users
}
export default async function Page() {
return (
<main>
<Users />
</main>
)
}

Good to know:

  • If use cache is added only to the layout or the page, only that route segment and any components imported into it will be cached.

Caching a component’s output with use cache

Section titled “Caching a component’s output with use cache”

You can use use cache at the component level to cache any fetches or computations performed within that component. The cache entry will be reused as long as the serialized props produce the same value in each instance.

export async function Bookings({ type = 'haircut' }: BookingsProps) {
'use cache'
async function getBookingsData() {
const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
return data
}
return //...
}
interface BookingsProps {
type: string
}
export async function Bookings({ type = 'haircut' }) {
'use cache'
async function getBookingsData() {
const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
return data
}
return //...
}

Since you can add use cache to any asynchronous function, you aren’t limited to caching components or routes only. You might want to cache a network request, a database query, or a slow computation.

export async function getData() {
'use cache'
const data = await fetch('/api/data')
return data
}
export async function getData() {
'use cache'
const data = await fetch('/api/data')
return data
}

In React, composition with children or slots is a well-known pattern for building flexible components. When using use cache, you can continue to compose your UI in this way. Anything included as children, or other compositional slots, in the returned JSX will be passed through the cached component without affecting its cache entry.

As long as you don’t directly reference any of the JSX slots inside the body of the cacheable function itself, their presence in the returned output won’t affect the cache entry.

export default async function Page() {
const uncachedData = await getData()
return (
// Pass compositional slots as props, e.g. header and children
<CacheComponent header={<h1>Home</h1>}>
{/* DynamicComponent is provided as the children slot */}
<DynamicComponent data={uncachedData} />
</CacheComponent>
)
}
async function CacheComponent({
header, // header: a compositional slot, injected as a prop
children, // children: another slot for nested composition
}: {
header: ReactNode
children: ReactNode
}) {
'use cache'
const cachedData = await fetch('/api/cached-data')
return (
<div>
{header}
<PrerenderedComponent data={cachedData} />
{children}
</div>
)
}
export default async function Page() {
const uncachedData = await getData()
return (
// Pass compositional slots as props, e.g. header and children
<CacheComponent header={<h1>Home</h1>}>
{/* DynamicComponent is provided as the children slot */}
<DynamicComponent data={uncachedData} />
</CacheComponent>
)
}
async function CacheComponent({
header, // header: a compositional slot, injected as a prop
children, // children: another slot for nested composition
}) {
'use cache'
const cachedData = await fetch('/api/cached-data')
return (
<div>
{header}
<PrerenderedComponent data={cachedData} />
{children}
</div>
)
}

You can also pass Server Actions through cached components to Client Components without invoking them inside the cacheable function.

import ClientComponent from './ClientComponent'
export default async function Page() {
const performUpdate = async () => {
'use server'
// Perform some server-side update
await db.update(...)
}
return <CachedComponent performUpdate={performUpdate} />
}
async function CachedComponent({
performUpdate,
}: {
performUpdate: () => Promise<void>
}) {
'use cache'
// Do not call performUpdate here
return <ClientComponent action={performUpdate} />
}
import ClientComponent from './ClientComponent'
export default async function Page() {
const performUpdate = async () => {
'use server'
// Perform some server-side update
await db.update(...)
}
return <CachedComponent performUpdate={performUpdate} />
}
async function CachedComponent({ performUpdate }) {
'use cache'
// Do not call performUpdate here
return <ClientComponent action={performUpdate} />
}
'use client'
export default function ClientComponent({
action,
}: {
action: () => Promise<void>
}) {
return <button onClick={action}>Update</button>
}
'use client'
export default function ClientComponent({ action }) {
return <button onClick={action}>Update</button>
}

Set NEXT_PRIVATE_DEBUG_CACHE=1 for verbose cache logging:

Terminal window
NEXT_PRIVATE_DEBUG_CACHE=1 npm run dev
# or for production
NEXT_PRIVATE_DEBUG_CACHE=1 npm run start

Good to know: This environment variable also logs ISR and other caching mechanisms. See Verifying correct production behavior for more details.

In development, console logs from cached functions appear with a Cache prefix.

If your build hangs, you’re accessing Promises that resolve to dynamic or runtime data, created outside a use cache boundary. The cached function waits for data that can’t resolve during the build, causing a timeout after 50 seconds.

When the build timeouts you’ll see this error message:

Error: Filling a cache during prerender timed out, likely because request-specific arguments such as params, searchParams, cookies() or dynamic data were used inside “use cache”.

Common ways this happens: passing such Promises as props, accessing them via closure, or retrieving them from shared storage (Maps).

Good to know: Directly calling cookies() or headers() inside use cache fails immediately with a different error, not a timeout.

Passing runtime data Promises as props:

import { cookies } from 'next/headers'
import { Suspense } from 'react'
export default function Page() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Dynamic />
</Suspense>
)
}
async function Dynamic() {
const cookieStore = cookies()
return <Cached promise={cookieStore} /> // Build hangs
}
async function Cached({ promise }: { promise: Promise<unknown> }) {
'use cache'
const data = await promise // Waits for runtime data during build
return <p>..</p>
}

Await the cookies store in the Dynamic component, and pass a cookie value to the Cached component.

Shared deduplication storage:

// Problem: Map stores dynamic Promises, accessed by cached code
import { Suspense } from 'react'
const cache = new Map<string, Promise<string>>()
export default function Page() {
return (
<>
<Suspense fallback={<div>Loading...</div>}>
<Dynamic id="data" />
</Suspense>
<Cached id="data" />
</>
)
}
async function Dynamic({ id }: { id: string }) {
// Stores dynamic Promise in shared Map
cache.set(
id,
fetch(`https://api.example.com/${id}`).then((r) => r.text())
)
return <p>Dynamic</p>
}
async function Cached({ id }: { id: string }) {
'use cache'
return <p>{await cache.get(id)}</p> // Build hangs - retrieves dynamic Promise
}

Use Next.js’s built-in fetch() deduplication or use separate Maps for cached and uncached contexts.

Deployment OptionSupported
Node.js serverYes
Docker containerYes
Static exportNo
AdaptersPlatform-specific

Learn how to configure caching when self-hosting Next.js.

VersionChanges
v16.0.0"use cache" is enabled with the Cache Components feature.
v15.0.0"use cache" is introduced as an experimental feature.

View related API references.

  • use cache: private
    • Learn how to use the “use cache: private” directive to cache functions that access runtime request APIs.
  • cacheComponents
    • Learn how to enable the cacheComponents flag in Next.js.
  • cacheLife
    • Learn how to set up cacheLife configurations in Next.js.
  • cacheHandlers
    • Configure custom cache handlers for use cache directives in Next.js.
  • cacheTag
    • Learn how to use the cacheTag function to manage cache invalidation in your Next.js application.
  • cacheLife
    • Learn how to use the cacheLife function to set the cache expiration time for a cached function or component.
  • revalidateTag
    • API Reference for the revalidateTag function.