`use cache: private` Explained: Per-User Caching

use cache: private caches results per user in the browser only. Learn what it allows, what it never stores on the server, and when to reach for it.

7 min read

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:

typescripttypescript
// 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.
  • searchParams is 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

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.
RunePowered by Rune AI

Frequently Asked Questions

Does use cache: private store results on the server?

No. Results are cached only in the browser's memory and do not persist across page reloads. The function still runs on every server render.

Which request APIs can I read inside a private cache?

cookies(), headers(), and searchParams are allowed. The connection() function is prohibited because it provides connection-specific data.

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.