skip to content

In the Next.js App Router, what does passing `{ cache: 'force-cache' }` to `fetch` inside a Server Component do, and is that already what a plain `fetch` with no options does?

level: middleimportance: must knowfreq 78%

answer

  1. opt-in, not automatic
  2. the default moved between majors
  3. a shared server-side store of responses
  4. force-cache writes the Data Cache entry
  5. bare fetch is uncached in Next 15+

basics

~20 s

Passing cache: 'force-cache' stores that fetch's response in Next's server-side Data Cache so later requests reuse it instead of calling the origin. In Next 15 and 16 that is not the default: a bare fetch is uncached.

solid answer

~50 s

`cache: 'force-cache'` tells Next to put that response into the **Data Cache** — a persistent, server-side store that lives across requests — and to serve later matching requests from it instead of calling the origin again. It is an explicit opt-in: since Next 15, and still in Next 16, a bare `fetch` in a Server Component is not cached, so it hits the origin on every render. That is a reversal of Next 13.4–14, where `fetch` behaved like `force-cache` by default and you opted *out* with `cache: 'no-store'` — which is exactly why the same snippet caches in one repo and not in another. The entry is keyed from the request URL plus the options you passed, and it survives until something revalidates it, either a time set through `next.revalidate` or an on-demand invalidation. Because the store is shared by every request, only opt in data that is safe for all users to see.

code

typescript · 11 lines
typescript
export async function getProducts() {
  // Opted in: the response is written to the Data Cache and reused.
  const cached = await fetch('https://api.example.com/products', {
    cache: 'force-cache',
  })

  // Not opted in: under Next 15/16 defaults this calls the origin every time.
  const live = await fetch('https://api.example.com/products')

  return [await cached.json(), await live.json()]
}

go deeper

for a junior

Know that in a Server Component you can ask Next to remember a fetch response by passing cache: 'force-cache', and that in current Next a fetch with no options is not remembered.

for a middle

Be ready to explain that the option writes into the server-side Data Cache, that the entry is keyed from the URL plus the options you passed, and that the default flipped between Next 14 and Next 15.

for a senior

Show that you reason about which fetches are safe to share across all users, and that you would answer a caching question by naming the version whose default you are quoting instead of stating one as universal.

for a principal

Own the policy: decide as a codebase which categories of data may enter a shared server-side cache at all, and make that decision visible in review rather than leaving each fetch to whatever the framework default happens to be this major version.

## The `fetch` in a Server Component is not the browser's `fetch` On the server, Next.js replaces the global `fetch` with its own wrapper. The wrapper still accepts everything the Web `fetch` accepts, and it reads two things to decide what to do with the response: the standard `cache` option, and a Next-only `next` option that carries `revalidate` and `tags`. Nothing about this applies in the browser — code that runs client-side gets the platform `fetch`, which ignores those hints entirely. ```ts // Server Component / server module const res = await fetch('https://api.example.com/products', { cache: 'force-cache', }) ``` ## What `force-cache` actually means here `force-cache` opts the request into the **Data Cache**: a server-side store of *fetch results*, persisted outside the lifetime of a single request. When a later request produces the same cache key, Next returns the stored body without going to the origin at all. Two consequences follow immediately. First, the store is **shared**. It is not per-user, per-session, or per-tab; it is one server-side store that every incoming request consults. Caching a response that differs per user is therefore a data-leak shape, not just a staleness bug. Second, an entry stays until something makes it stale. `force-cache` on its own sets no expiry — it is not a TTL. Staleness comes from `next: { revalidate: N }` on the same fetch, or from an on-demand invalidation elsewhere in the app. ## How the entry is keyed Next derives the key from the request URL and the options you passed — the method, the headers, and the body. That is why two fetches to the same URL with different `Authorization` headers do not collide, and why appending a query parameter is a valid way to force a distinct entry. It is also why a fetch that carries no per-user distinguishing input produces exactly one entry that every visitor shares. ## The default moved between major versions This is the part interviewers actually probe, because it is the single most common source of a stale answer. - **Next 13.4 – 14**: `fetch` in the App Router defaulted to `force-cache`. Data was cached unless you wrote `cache: 'no-store'` or triggered dynamic rendering some other way. Teams were routinely surprised by build-time data frozen into a page. - **Next 15 and later (including Next 16)**: `fetch` is **not** cached by default. You opt in with `cache: 'force-cache'` or by supplying `next: { revalidate: N }`. Teams upgrading are now surprised in the opposite direction — origin traffic climbs on pages nobody edited. The safest way to answer is to describe the *mechanism* ("which option puts this into the Data Cache, and what evicts it") and then name the version whose default you are quoting, rather than asserting a universal default. ```ts // Next 15/16: cached, because an option opts it in await fetch(url, { cache: 'force-cache' }) await fetch(url, { next: { revalidate: 3600 } }) // Next 15/16: not cached — no opt-in await fetch(url) ``` ## `no-store`, the other end of the same switch `cache: 'no-store'` states the opposite intent: do not read from the Data Cache and do not write to it. Under the Next 15+ default it is largely redundant for a plain fetch, but it is still worth writing on genuinely per-request data, because it documents the intent and it keeps behaving correctly if a surrounding configuration or a future default nudges things toward caching. ## Why it matters in practice Three recurring failures come out of misreading this option. 1. **Stale content that nothing seems to refresh.** On Next 14 defaults, a CMS fetch is cached at build and never revisited because no revalidation was configured. The fix is to give the fetch a lifetime, not to sprinkle `no-store` everywhere. 2. **An origin suddenly under load after an upgrade.** The same code that was implicitly cached now calls out on every render. The fix is to opt the genuinely shareable fetches back in deliberately. 3. **One user seeing another's data.** A per-user endpoint was opted in, and the key did not vary per user. The fix is to keep personal data out of the Data Cache entirely. A good answer names the store (Data Cache), names the opt-in (`force-cache` or `next.revalidate`), states what the key is built from, and pins the default to a version rather than claiming it as universal.

  • If `force-cache` sets no expiry, how does an entry it created ever stop being served?
    Something has to mark it stale. Either the same fetch carries `next: { revalidate: N }`, which gives its entry a lifetime, or an on-demand invalidation elsewhere in the app targets it — for example through a tag you attached with `next: { tags: [...] }`. Without one of those, a `force-cache` entry can be served indefinitely, which is fine for genuinely immutable data and a bug for anything else.
  • Under the Next 15 and 16 defaults, is writing `cache: 'no-store'` on a fetch pointless?
    Functionally it is close to redundant for a plain fetch, since uncached is already the default. It still earns its place as intent: it says "this data is per-request" to the next reader, and it keeps the behaviour pinned if surrounding configuration or a future default leans toward caching. What it does not do is make the code faster or safer on its own.
  • Two Server Components on the same page fetch the same URL with `force-cache`. Does the origin see one request or two?
    One, and for two separate reasons. Within a single render pass Next deduplicates identical GET fetches, so the second call reuses the first call's result. Across requests, the Data Cache entry keyed from that URL and those options is reused rather than refetched. The two mechanisms are different layers, but from the origin's point of view the effect is the same: it is not called twice.

saying these in an interview costs you the question

  • Saying fetch is always cached by default in the App Router
  • Calling force-cache a TTL that expires on its own
  • Assuming the Data Cache is per-user or per-session
  • Thinking the option controls the browser's HTTP cache
  • Opting a per-user endpoint into the Data Cache for speed

context