use cache is a shared cache: every visitor gets the same entry, which is why request APIs are banned inside it. Two sibling directives cover what that rule excludes.
"use cache: private" may read cookies(), headers() and searchParams directly. Its result is never stored on the server; it is deduplicated within one request and kept in the browser's memory for the stale window.
async function greetingFor() {
"use cache: private";
cacheLife({ stale: 60 });
const theme = (await cookies()).get("theme")?.value ?? "light";
return `${theme} theme — private render #${bump("greeting")}`;
}Three requests, the first two with theme=light and the third with theme=dark:
light theme — private render #1 light theme — private render #2 dark theme — private render #3
The counter climbs on every request, which is the point: nothing was reused server-side. What cacheLife buys here is the client copy and a place in the per-link prefetch. connection() is rejected in private caches too.
"use cache: remote" is the opposite trade. It keeps the shared semantics of use cache but moves storage to a durable cache handler — Redis 2,763 , a KV store, whatever your platform configures through cacheHandlers — at the cost of a network round trip per lookup, which pays off only where the hit rate is high.
| use cache | use cache: remote | use cache: private | |
|---|---|---|---|
| Server storage | In-memory or handler | Remote handler | None |
| Scope | All users | All users | One browser |
| Reads cookies directly | No | No | Yes |
| Extra cost | None | Storage, latency | None |
Reach for remote when work is deferred to request time, because that is where the in-memory cache helps least: on serverless platforms each instance has its own memory and may be destroyed after one response, so shared entries are the only ones that survive. Neither directive survives a deployment, since the build ID is part of every cache key. Remote caches may nest inside remote or plain caches; private and remote may never nest inside one another, in either direction.