Type something to search...
Next.js use cache: private

Next.js use cache: private

Caching and personalization usually pull in opposite directions. Caching wants one answer you can hand out to everyone; personalization wants an answer that depends on who's asking. Next.js's Cache Components model gives you 'use cache' for the first case, but the moment a cached function needs to read a cookie, a header, or a search param to personalize its result, 'use cache' refuses outright — those runtime request APIs aren't allowed inside it, because a value cached from one visitor's request could otherwise leak into another visitor's response.

'use cache: private' is the directive built for exactly that gap. It lets a function read request-scoped data like cookies(), headers(), and searchParams, and still get a caching layer around it — but with a deliberate, important constraint: nothing gets stored on the server. Whatever this directive caches lives only in the requesting browser's memory, for that one visitor, and vanishes the moment the page reloads. Understanding that constraint is the whole point of this article, because it's the thing most people misjudge the first time they reach for it.

The problem this directive solves

Imagine a product page that shows personalized recommendations based on the visitor's session. The recommendations are expensive to compute — maybe they hit a recommendation engine or run a database query joining purchase history against a similarity model. You'd like to cache that work so a visitor doesn't pay the full cost on every render of the same page. But the input to that computation is a session ID pulled from a cookie, and the whole point is that different visitors get different answers.

Two options exist inside the Cache Components model:

  1. Refactor so the function that reads cookies() lives outside the cached function, and you pass the resolved session ID in as a plain argument. The cached function itself becomes pure — same input, same output — which is exactly what ordinary 'use cache' is designed for.
  2. Reach for 'use cache: private' when that refactor genuinely isn't practical — the runtime access is buried deep in a call chain, wrapped in a third-party client, or otherwise awkward to hoist out.

The docs are explicit that option 1 is the default recommendation, and option 2 is the escape hatch. There's also a second, less obvious reason to reach for it: compliance. If a regulation or internal policy says certain data can't be persisted on your servers even temporarily, a private cache that only ever exists in the visitor's own browser sidesteps that requirement entirely — there's no server-side artifact to worry about retention rules for.

What "never stored on the server" actually means

This is the detail that trips people up, so it's worth stating plainly: a 'use cache: private' result is cached in the browser's memory, not in Next.js's server-side cache store, not in your CDN, and not in whatever custom cache handler you might have configured for regular 'use cache' functions. Two direct consequences follow from that:

  • It does not survive a page reload. Navigate away and back, or hard-refresh, and the function runs fresh. This is not a durable cache in the sense that 'use cache' or ISR are — think of it more like a per-session memoization that only helps with client-side navigations within a single visit.
  • You cannot configure a custom cache handler for it. The docs state this directly — whatever storage backend you've wired up for your regular cached functions (Redis, a CDN-backed remote cache, whatever) is irrelevant here. There's no handler to configure because there's no server-side store to configure it for.

If you came into this expecting a per-user cache that persists across sessions or across server instances, this isn't that. It's much closer to "avoid recomputing this while the visitor is actively navigating around" than "avoid recomputing this ever."

Enabling the directive

'use cache: private' is part of the Cache Components feature, so it requires the cacheComponents flag turned on in your config:

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  cacheComponents: true,
};

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

module.exports = nextConfig;

Without this flag enabled project-wide, the directive isn't available at all — it's not something you can opt into on a per-function basis independently of the rest of your app's caching model.

A worked example

Here's the canonical shape: a product page that renders instantly (its core content doesn't depend on the visitor), with a personalized recommendations panel streamed in separately via Suspense.

import { Suspense } from "react";
import { cookies } from "next/headers";
import { cacheLife, cacheTag } from "next/cache";

export async function generateStaticParams() {
  return [{ id: "1" }];
}

export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;

  return (
    <div>
      <ProductDetails id={id} />
      <Suspense fallback={<div>Loading recommendations...</div>}>
        <Recommendations productId={id} />
      </Suspense>
    </div>
  );
}

async function Recommendations({ productId }: { productId: string }) {
  const recommendations = await getRecommendations(productId);

  return (
    <div>
      {recommendations.map((rec) => (
        <ProductCard key={rec.id} product={rec} />
      ))}
    </div>
  );
}

async function getRecommendations(productId: string) {
  "use cache: private";
  cacheTag(`recommendations-${productId}`);
  cacheLife({ stale: 60 });

  // Access cookies within private cache functions
  const sessionId = (await cookies()).get("session-id")?.value || "guest";

  return getPersonalizedRecommendations(productId, sessionId);
}

A few things worth noticing that the code alone doesn't spell out:

The directive lives on the function, not the component. getRecommendations is a plain async function, not the Recommendations component itself. This matters because it means you can wrap just the narrow piece of logic that needs runtime data, while everything around it — the component tree, the Suspense boundary, the rest of the page — stays outside that constraint.

cacheTag and cacheLife still apply. Even though nothing is persisted server-side, you still configure a cacheLife policy. That's not decorative — it directly controls behavior on the client side, which is the next section's subject.

The Suspense boundary is doing real work. Because this function touches cookies(), it can never run during static shell generation — Next.js excludes it from prerendering entirely, and it executes fresh on every server render. Wrapping it in Suspense lets the rest of the page (ProductDetails) stream out immediately while this personalized slice resolves separately, rather than blocking the whole page on a per-request computation.

Request APIs: what's allowed and what isn't

'use cache: private' widens what's allowed compared to plain 'use cache', but it doesn't open every door:

APIAllowed in use cacheAllowed in use cache: private
cookies()NoYes
headers()NoYes
searchParamsNoYes
connection()NoNo

connection() staying off-limits in both cases is worth pausing on. It's not an oversight — connection() exposes information specific to the individual network connection, which is a level below "this visitor's session" and arguably not something any caching layer, however private, should be pretending to memoize. If your code needs connection(), that logic has to live outside any cached function, private or not.

The cacheLife timing requirements you can't skip

The docs call out two thresholds that aren't obvious from the API surface alone:

  • The stale time in your cacheLife config must be at least 30 seconds for per-link prefetching to work correctly.
  • It must be at least 5 minutes for the private-cached content to be eligible for inclusion in the route's App Shell (the static portion of the page that gets served instantly, before any per-request work resolves).

If you set cacheLife({ stale: 5 }) because the underlying data changes every few seconds, you'll get correct behavior in the narrow sense — no stale data — but you'll silently lose prefetching benefits and App Shell inclusion for that content. This is the kind of thing that's easy to miss because nothing throws an error; the page just quietly performs worse than it could.

How this compares to the other cache directives

Next.js's Cache Components model gives you three closely related but distinct directives, and mixing them up is a common source of confusion:

DirectiveServer-side storageRuntime API accessTypical use
use cacheYes (shared across all visitors)NoData that's the same for everyone — a blog post, a product catalog page
use cache: privateNo (browser memory only)cookies(), headers(), searchParamsPersonalized results derived from a session, where refactoring the runtime read out isn't practical
use cache: remoteYes (persistent, shared)NoSame as use cache, but explicitly backed by a remote cache handler rather than in-memory

The naming is deliberately parallel — all three share the use cache prefix precisely because they're variations on one idea (cache this function's output) with different rules about where the data can come from and where the result can live. If you're reaching for 'use cache: private' because you assumed it was just "the version of use cache that also does server-side storage but scoped per-user," that's the wrong mental model — that's not a feature Cache Components currently offers. Server-side per-user caching still means writing your own logic (a keyed lookup in Redis by session ID, for instance) outside the directive system entirely.

When you shouldn't reach for this

Given how easy it is to add 'use cache: private' to any function with a runtime read buried in it, it's worth being explicit about when not to:

  • When the refactor to hoist the runtime read out is actually straightforward. If cookies() is called at the top of your Server Component and the value could just be passed down as a prop to a plain use cache function, do that instead — you get server-side, cross-visitor caching, which is strictly more valuable than per-browser memoization.
  • When you need the result to survive a page reload. It won't. If durability matters, you need a real persistence layer (a session store, a database-backed cache, a signed cookie holding the computed result itself) — not this directive.
  • When the personalized computation is cheap anyway. Caching adds a small amount of bookkeeping overhead. If getRecommendations above were a single indexed database lookup, the caching layer might not be earning its keep, especially given it only helps within a single browser session's lifetime.

Key takeaways

QuestionAnswer
Where is the result stored?In the browser's memory only — never on the server
Does it survive a page reload?No
Which runtime APIs can it access?cookies(), headers(), searchParams
Which runtime API is still off-limits?connection()
Can you use a custom cache handler?No — there's no server-side store to configure one for
Minimum stale time for prefetching30 seconds
Minimum stale time for App Shell inclusion5 minutes
Requires which config flag?cacheComponents: true

'use cache: private' exists for a narrow, specific situation: a function that both needs to read request-scoped data and would benefit from not recomputing that read on every single render within a visit. It's not a general-purpose personalization cache, and it's not a substitute for server-side per-user storage. Reach for it when hoisting the runtime read out of the cached function isn't practical, or when compliance rules mean you specifically don't want server-side persistence — and reach for a real persistence layer instead when you do need the result to last longer than one browser session.

Tags :
Share :

Related Posts

Can Next.js Be Used with GraphQL?

Can Next.js Be Used with GraphQL?

Next.js and GraphQL are two powerful technologies that have gained significant traction in the web development community. Next.js, a React-based fram

Dive Deeper
How does Next.js differ from Create React App?

How does Next.js differ from Create React App?

In the world of modern web development, React.js has emerged as a dominant force due to its flexibility, performance, and extensive ecosystem. Two po

Dive Deeper
How does Next.js handle image optimization?

How does Next.js handle image optimization?

In modern web development, image optimization plays a critical role in enhancing user experience and improving site performance. Large, unoptimized i

Dive Deeper