The use cache: private directive is a caching variant that can read request data inside the cached scope. It requires Cache Components, like regular use cache, but its results are never stored on the server. They live in the browser's memory only and do not survive a page reload.
The directive is how you give a lifetime to a function that reads cookies directly:
// app/lib/recommendations.ts
import { cookies } from 'next/headers'
import { cacheLife } from 'next/cache'
export async function getRecommendations(productId: string) {
'use cache: private'
cacheLife({ stale: 60 })
const sessionId = (await cookies()).get('session-id')?.value || 'guest'
const res = await fetch(`https://api.example.com/recommendations?product=${productId}&session=${sessionId}`)
return res.json()
}The function reads cookies, which regular use cache forbids, and the result is kept per user in the browser. The stale time of 60 seconds means the client reuses that value for a minute before checking again.
Why results stay in the browser
The server never keeps an entry for this directive. Every server render re-executes the function, and the only copy of the result lives in the browser for the stale window. Because it reads request data, the function is also excluded from static shell generation.
That exclusion has a practical consequence: the component that awaits a private cached function renders at request time, so it belongs inside a Suspense boundary. Without one, the surrounding route cannot produce a shell and the build reports uncached data outside Suspense.
This makes it the opposite of the remote variant, which shares results across users in a durable store. Two users never share a result here, and the same user gets a fresh one after a full reload. Because nothing is written to a shared store, the directive also suits data that must not be persisted server-side, even briefly.
Request APIs allowed
cookies()is allowed inside the scope.headers()is allowed inside the scope.searchParamsis allowed inside the scope.connection()is prohibited in both private and regular use cache.
There is no custom cache handler for these results, because nothing is stored server-side. Reading one of these APIs inside a regular use cache scope fails with the next-request-in-use-cache error, which is why this variant exists.
Prefetching and stale time minimums
During a client navigation, the result can join the per-link prefetch. The function executes on the server, reads runtime data directly, and caches the result in the browser as part of that prefetch, so the next page already has its content ready before the click.
The stale time controls how long the browser trusts that copy, and two thresholds decide how far it reaches.
- A stale time of at least 30 seconds is required for per-link prefetching to work.
- A stale time of at least 5 minutes is required for the result to join the route's App Shell.
Set the value deliberately. A result that misses the App Shell still helps on navigation, just not on the initial static shell.
When to use it
Reach for use cache: private when you cannot refactor to pass runtime values as arguments to a shared cached function, or when compliance rules forbid storing certain data on the server even temporarily. For example, a page that shows a visitor's own order history can cache it privately, while a public product description stays in a shared use cache.
In most cases the cleaner pattern is to read the cookie outside a cached function and pass the extracted value in as an argument. That keeps one shared entry per value instead of one entry per browser. For that pattern, see the use cache directive.
Common mistakes
- Assuming these results persist server-side. They never touch server storage.
- Using this directive for data that could instead be shared per language or region.
- Nesting a remote cache inside a private scope, which is not allowed.
- Setting no cacheLife, so the stale time falls back to the default profile instead of the short window you intended.
For shared, durable caching across instances, use the remote variant. For how stale, revalidate, and expire interact, see cacheLife explained.
Rune AI
Key Insights
- use cache: private can read cookies, headers, and searchParams.
- Results live only in the browser and never on the server.
- The function still executes on every server render.
- connection() stays prohibited inside private caches.
- Prefer passing runtime values as arguments to a shared use cache.
Frequently Asked Questions
Does use cache: private store results on the server?
Which request APIs can I read inside a private cache?
Conclusion
use cache: private lets a cached scope read request data while keeping results out of server storage. It is a per-user, browser-only cache that runs on every server render, so use it only when you cannot pass runtime values into a shared cached function.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.