skip to content

In a Next.js App Router Server Component, a developer adds `cache: 'force-cache'` to a fetch of an endpoint that returns the signed-in user's own profile. Why is that dangerous, and what decides whether two different visitors hit the same cached entry?

level: seniorimportance: should knowfreq 44%

answer

  1. one store, many users
  2. who is in the key?
  3. cookies are not forwarded implicitly
  4. URL plus options is the whole key
  5. per-user entries are a different failure

basics

~20 s

Next's Data Cache is one server-side store shared by every request, and its entries are keyed from the fetch URL plus the options passed. If nothing in that key varies per user, the first visitor's profile is served to everyone else.

solid answer

~50 s

The Data Cache is not per-user — it is a single server-side store that every incoming request consults, and Next derives each entry's key from the request **URL plus the options you passed**: method, headers, body. So the question "do two visitors collide?" reduces to "does anything user-specific appear in that key?" If the endpoint is a bare path like `/api/me` and the caller's identity travels some other way, every request produces the same key and the first response cached becomes everyone's response. That is a cross-user data leak, not a staleness bug. Putting the token in a header does avoid the collision, but it trades one problem for another: you now write one entry per user for data that is read once, filling a shared cache with things nobody reuses. The right answer for personal data is not to opt it into the Data Cache at all — leave it uncached, or `cache: 'no-store'` to make the intent explicit.

go deeper

for a junior

Know that anything Next caches on the server is shared by all visitors, so data that belongs to one signed-in user should not be given cache: 'force-cache'.

for a middle

Be ready to explain that the entry key comes from the fetch URL and the options you passed, and that a Server Component does not attach the incoming request's cookies to an outbound fetch by itself.

for a senior

Show that you classify each fetch by audience before choosing a caching option, and that you can reason about the second failure mode too: keying per user avoids the leak but produces a cache full of entries nobody reuses.

for a principal

Own the rule and its enforcement: define which classes of data may enter a shared server-side cache, and make that decision reviewable — a per-request identity in a cache key should be a design discussion, not an individual author's judgment call.

## The store is shared, and that is the whole point The Data Cache exists so that work done for one request can be reused by the next one — across users, across sessions, across renders. There is no ambient notion of "the current user" attached to an entry. When you write `cache: 'force-cache'`, you are saying *this response is a fact about the world, not about the caller*. Most caching bugs are about freshness. This one is about audience, and it is much more expensive. ## What the key is built from Next derives the entry key from the request URL and the options handed to `fetch` — the method, the headers, the body. Nothing else. Notably, the ambient request context does not silently join the key: a Server Component does not automatically forward the incoming request's cookies to an outbound fetch, so unless you explicitly attached something identifying, two visitors produce byte-identical keys. ```ts // Same key for every visitor: identity is nowhere in the URL or options. const me = await fetch('https://api.example.com/me', { cache: 'force-cache' }) ``` Walk it through: visitor A renders, the endpoint returns A's profile, the entry is written. Visitor B renders a minute later; the key matches; B is served A's profile without a single request leaving the server. Nothing errors, nothing logs, and in an internal tool the bug can live for months because it only shows when two people compare screens. ## "Then I'll put the token in a header" Adding an `Authorization` header does change the key, so the collision goes away. But look at what the cache now holds: one entry per user, for a response that is typically read once per session and is stale the moment the user edits their profile. You have converted a shared cache into a per-user store with no eviction policy you control, which is a poor use of a resource whose value comes from being shared. There is a second-order version of the same mistake with any per-request header — a request ID, a trace header, a locale that varies widely. Each distinct value forks a new entry, the hit rate collapses, and the cache grows while doing nothing useful. ## What to do instead Split the fetches by audience, not by convenience: - **Shared, non-personal data** — catalogues, published content, configuration, reference data — opts in with `force-cache` or a `revalidate` interval. It is the same for everyone, so sharing it is correct. - **Personal or authorisation-dependent data** — profiles, carts, entitlements, anything the answer changes with who is asking — stays out of the Data Cache. Under Next 15 and 16 defaults leaving the options off already achieves that, and writing `cache: 'no-store'` states the intent for the next reader. A page usually needs both, and that is fine: the shared parts are cached, the personal parts are not, and they compose in the same render. ## How to catch it - In review, treat `force-cache` on any URL containing `me`, `account`, `session`, `cart`, or `admin` as something to justify out loud. - Ask of each cached fetch: *if I served this exact bytes to a stranger, would that be fine?* If the answer is no, it does not belong in a shared cache. - When a report of "I saw someone else's name" arrives, the shared-key hypothesis is worth checking before session handling, because it is silent and it survives restarts of the process that first cached it. ## The one-line version The Data Cache is keyed by the request you made, not by the user you made it for. Anything whose correct answer depends on who is asking must not be opted into it.

  • Does reading the incoming request's cookies in the Server Component change the key of an outbound cached fetch?
    Not on its own. The key is built from what you passed to `fetch`. Reading cookies and then attaching the value to the outbound request as a header does change the key; reading them and doing nothing with them does not. That asymmetry is exactly why the leak is easy to write — the code looks user-aware while the request that gets cached is not.
  • A page needs both a public product list and the visitor's cart. How do you cache it?
    Separately. The product list is the same for everyone, so it opts into the Data Cache with `force-cache` or an interval. The cart depends on who is asking, so it stays uncached. There is no need to choose one policy for the page — two fetches, two decisions, and the shared half still gets the benefit.
  • What is the downside of keying per user with an Authorization header rather than skipping the cache?
    You fill a shared store with entries that are read roughly once each. The hit rate approaches zero, the cache grows with your user count, and the data is often stale by the time it would be reused. The cache is valuable because entries are shared; per-user entries pay the storage cost without collecting the benefit.

saying these in an interview costs you the question

  • Assuming the Data Cache is scoped per user or session
  • Thinking incoming cookies are forwarded to outbound fetches automatically
  • Treating a cross-user cache hit as merely a staleness bug
  • Adding the auth token to the key and calling the problem solved
  • Caching personal data because the endpoint is fast to call anyway

context