ML
Next.js

The Four Caches of Next.js: Why Your Page Won't Update

Request memoization, the Data Cache, the Full Route Cache, and the Router Cache — what each layer stores, when it invalidates, and which one is serving you stale data right now.

June 13, 202610 min readNext.jsReact

"I deployed, but the page still shows old data" is the most-asked Next.js question, and the answer is almost always: you're looking at the wrong cache. The App Router stacks four distinct caching layers, each with its own scope, lifetime, and invalidation story. Here they are from innermost to outermost.

1. Request Memoization (per render)

During a single server render, identical fetch(url, options) calls are deduped — the request fires once and every component gets the same promise. This is why you can call the same getUser() in a layout and a page without paying twice.

// Both components call this; the network sees ONE request per render
async function getUser(id: string) {
  const res = await fetch(`https://api.example.com/users/${id}`);
  return res.json();
}

Scope: one render pass. You never invalidate it; it dies with the request. Note it only applies to fetch — for database clients, wrap the call in React's cache() to get the same behavior.

2. Data Cache (per fetch, persistent)

The result of a fetch on the server is stored across requests and deployments unless you say otherwise. This is the layer that surprises people.

fetch(url)                                  // cached indefinitely (static)
fetch(url, { cache: "no-store" })           // never cached
fetch(url, { next: { revalidate: 60 } })    // stale-while-revalidate, 60s
fetch(url, { next: { tags: ["posts"] } })   // invalidate on demand

On-demand invalidation happens in Server Actions or route handlers: revalidateTag("posts") or revalidatePath("/blog"). If your CMS webhook isn't calling one of these, your content updates will sit invisible until the next revalidate window.

3. Full Route Cache (per route, build/ISR)

For static routes, Next.js caches the rendered output — HTML plus the RSC payload — at build time. A route is static unless something opts it out: a dynamic function (cookies(), headers(), searchParams), cache: "no-store" on a fetch, or an explicit export const dynamic = "force-dynamic".

Key interaction: revalidateTag purges the Data Cache and the affected route's Full Route Cache together. But a redeploy clears the Full Route Cache while the Data Cache survives deployments — so "I deployed and it's still stale" usually means a fetch with a long revalidate is feeding fresh builds old data.

4. Router Cache (client-side, per session)

The browser keeps visited route segments in memory so back/forward navigation is instant. Even after the server has fresh data, a client that already visited the page can keep showing the cached version for the duration of its staleness window.

// Force the client to refetch the current route's server components
import { useRouter } from "next/navigation";
const router = useRouter();
router.refresh();

Server Actions that call revalidatePath/revalidateTag also purge the Router Cache for the affected routes — one reason mutations belong in Server Actions rather than ad-hoc API calls.

The debugging checklist

  • Stale after CMS edit, fixes itself later → Data Cache; wire the webhook to revalidateTag.
  • Stale after deploy → Data Cache surviving the deploy; check fetches with long revalidate.
  • Fresh in incognito, stale in your tab → Router Cache; router.refresh() or navigate hard.
  • Route renders at request time when you expected static → something opted it out; hunt the cookies()/no-store call.
  • When in doubt, build locally: next build prints each route as ○ (static) or ƒ (dynamic).
SharePostLinkedIn

Reader Discussion

2 replies// weighed in

TopNewestAuthor
Add to the thread
Disagree, agree harder, or share your own experience…
Email instead →markdown okbe kind
  1. Isabella Costa· Junior EngineerKind words

    saved this. sharing at standup tomorrow — we've had exactly this problem for 2 sprints and nobody on the team had framed it this way 🙏

    Jun 15, 2026·2 days later
  2. Kenta Yamada· Tech LeadAsks

    would love a war-story follow-up. principles are clear; the actual debugging session is where the interesting stuff lives. there's a real shortage of "here's the dashboard, here's the thread we pulled, here's where we got stuck for 90 mins" content.

    Jun 17, 2026·4 days later

Worked on something similar? Email ducminhldm@gmail.com — I read every one. The good ones become future posts.

Comments seeded · live discussion via email