How to Fix: Server fetch is cached even when use “no-store” fetch cache value

6 min read

Why a Server fetch() Still Looks Cached Even with cache: 'no-store' in Next.js

You set cache: 'no-store' on a server-side fetch(), rebuild, run the app, and still get the same response. That behavior is confusing because it appears to contradict the Fetch API contract. In this case, the real issue is usually not that fetch ignores no-store, but that multiple caching layers in Next.js can make the result look cached.

In the reproduction linked in the issue, the key detail is that this happens during server rendering in a Next.js app. In modern Next.js, caching is not controlled by one switch alone. A request can be affected by:

  • Fetch cache for the individual request
  • Route-level static optimization
  • Full Route Cache for rendered output
  • Request memoization during a single render pass
  • Build-time prerendering when a route is detected as static

If the route itself is treated as static, the page output may be generated once and then reused, even if the underlying fetch was intended to be dynamic. That is why developers often conclude that cache: 'no-store' is being ignored, when the actual cause is broader rendering and caching behavior.

Understanding the Root Cause

The root cause is that cache: 'no-store' only affects the fetch request, not every other optimization in the rendering pipeline.

In Next.js, a server component can be analyzed as either static or dynamic. If Next.js decides the route is static, it may prerender the result during build time or cache the rendered HTML/RSC payload. In that situation, your fetch may have been executed earlier than you expect, and what you are seeing later is the cached route output rather than a newly executed fetch.

There are several technical reasons this can happen:

  • Static route optimization: If the page does not opt into dynamic rendering strongly enough, Next.js may prerender it.
  • Full Route Cache: Even if a fetch is marked no-store, the route response may still be reused depending on how the route is classified.
  • Build vs runtime confusion: Testing after yarn build can expose static optimization that was not obvious in development mode.
  • Memoization inside a render pass: Repeated identical fetches in the same render tree can return the same in-memory result during one request lifecycle.

So the bug is best understood like this: the fetch option is correct, but it is not sufficient when the route or render output is still eligible for caching.

Step-by-Step Solution

To fix this reliably, make the route explicitly dynamic and ensure the fetch is also marked as uncached.

1. Keep cache: 'no-store' on the fetch call

This tells Next.js and the underlying fetch layer not to persist the fetch result.

async function getData() {
  const res = await fetch('https://example.com/api/data', {
    cache: 'no-store'
  });

  if (!res.ok) {
    throw new Error('Failed to fetch data');
  }

  return res.json();
}

2. Force the route to render dynamically

If the page is being statically optimized, explicitly disable that behavior.

export const dynamic = 'force-dynamic';

In a page or layout file, that looks like this:

export const dynamic = 'force-dynamic';

async function getData() {
  const res = await fetch('https://example.com/api/data', {
    cache: 'no-store'
  });

  if (!res.ok) {
    throw new Error('Failed to fetch data');
  }

  return res.json();
}

export default async function Page() {
  const data = await getData();

  return (
    <main>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </main>
  );
}

3. If needed, use a zero revalidation window

Some teams prefer using route segment config to make freshness explicit.

export const revalidate = 0;

This can be used alongside dynamic rendering when you want to communicate that the route should not be statically reused.

4. Test in production mode correctly

Because this issue often shows up after yarn build, verify behavior using the production server:

yarn build
yarn start

Then hit the page multiple times and confirm the server executes a fresh request each time.

5. Add logging to prove which layer is caching

Log both the page render and the upstream API call. This helps determine whether the page is rerendering and whether the fetch is actually re-executed.

export const dynamic = 'force-dynamic';

async function getData() {
  console.log('Running fetch on server at', new Date().toISOString());

  const res = await fetch('https://example.com/api/data', {
    cache: 'no-store'
  });

  return res.json();
}

export default async function Page() {
  console.log('Rendering page at', new Date().toISOString());

  const data = await getData();
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

If the page log does not change between requests, the problem is likely route caching or static prerendering. If the page rerenders but the data is unchanged, inspect the upstream API, reverse proxy, or any platform cache in front of it.

6. Prefer a minimal known-good pattern

For issue reproduction and debugging, use the smallest possible server component:

export const dynamic = 'force-dynamic';
export const revalidate = 0;

export default async function Page() {
  const res = await fetch('https://example.com/api/time', {
    cache: 'no-store'
  });

  const data = await res.json();

  return (
    <main>
      <h1>Server Time</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </main>
  );
}

This removes ambiguity and makes it easier to confirm whether the framework or another layer is responsible.

Common Edge Cases

1. The upstream API is cached elsewhere

If your external API is behind a CDN, proxy, or platform cache, Next.js may be doing the right thing while the upstream response remains stale. Inspect response headers and test the API independently.

2. You are testing in development and production interchangeably

Dev mode and production mode do not always behave the same. Static optimization and route caching behavior are much easier to see after build and start.

3. A parent layout is affecting route behavior

If a parent layout.tsx or segment config is static, it can influence how the subtree behaves. Make sure the route segment containing the fetch is not unintentionally constrained by parent configuration.

4. Multiple identical fetches are memoized per request

Within a single server render, Next.js can memoize identical fetch calls. That is not persistent caching across requests, but it can still be surprising during debugging.

5. Mixing revalidate and cache settings inconsistently

Conflicting route and fetch policies can create confusing results. If you want truly dynamic behavior, use a consistent strategy: dynamic = 'force-dynamic', revalidate = 0, and cache: 'no-store'.

6. Client-side navigation makes the behavior look stale

If you navigate between pages in the app router, some cached client-side state or previously streamed payloads can make the result appear old. Test with hard refreshes and direct requests when isolating the issue.

FAQ

Does cache: 'no-store' disable all caching in Next.js?

No. It disables caching for that specific fetch request, but it does not automatically disable route-level caching, static prerendering, or every framework optimization.

Why does this usually appear after yarn build?

Because production builds enable static analysis and caching optimizations more aggressively. A page that seems dynamic in development may be prerendered or cached in production if it is not explicitly marked dynamic.

What is the safest fix for this issue?

The most reliable fix is to combine cache: 'no-store' with export const dynamic = 'force-dynamic'. If needed, also add export const revalidate = 0 to make the route behavior fully explicit.

For the original issue, the practical takeaway is simple: when a server fetch appears cached despite no-store, treat it as a route rendering mode problem first, not just a fetch option problem. Once the route is forced dynamic, the fetch behavior typically matches expectations.

For deeper framework details, review the Next.js caching documentation and compare it with the reproduction linked in the issue repository.

Leave a Reply

Your email address will not be published. Required fields are marked *