The Four Caching Layers

Request Memoization, the Data Cache, the Full Route Cache, and the Router Cache — what each one caches, its scope, and its lifetime.

Next.js caching confuses people because it is not one cache — it is four, stacked, each with a different scope and lifetime. Once you can name the layer a value lives in, 'why is my data stale?' becomes a five-second diagnosis instead of an afternoon.

The four layers

LayerCachesWhereLifetime
Request Memoizationduplicate fetch() in one renderserversingle request
Data Cachefetch() results across requestsserver (persistent)until revalidated
Full Route Cacherendered HTML + RSC payloadserver (build/persistent)until revalidated/redeployed
Router CacheRSC payload of visited routesclient (in-memory)seconds to a session

How a request flows through them

  1. Request Memoization: within one render pass, calling fetch(sameUrl) twice hits the network once. It is React's cache, automatic for fetch, and dies when the request ends. This is why you can fetch the same data in a layout and a page without a waterfall.
  2. Data Cache: persists fetch results between requests and users. Controlled by cache and next.revalidate. This is the layer revalidateTag talks to.
  3. Full Route Cache: for static routes, the rendered output is cached so requests skip rendering entirely.
  4. Router Cache: on the client, visited routes' payloads are held so back/forward and re-visits are instant — and this is the one that most often makes people think 'my update didn't apply' after a mutation.

Example

Example · typescript
// Request Memoization in action — no waterfall, one network call.
// Both the layout and the page call the same fetch; React
// dedupes it within the single server render.
async function getUser(id: string) {
  // identical URL + options => memoized for this request
  const res = await fetch(`https://api.example.com/users/${id}`, {
    next: { tags: [`user:${id}`], revalidate: 3600 }, // Data Cache
  });
  return res.json();
}

// app/users/[id]/layout.tsx
export default async function Layout({ params, children }: any) {
  const { id } = await params;
  const user = await getUser(id); // network hit #1
  return <header>{user.name}{children}</header>;
}

// app/users/[id]/page.tsx
export default async function Page({ params }: any) {
  const { id } = await params;
  const user = await getUser(id); // memoized — NO second network hit
  return <p>{user.email}</p>;
}

When to use it

  • A developer avoids double-fetching user data in a layout and page by relying on Request Memoization to dedup identical fetch calls within one render.
  • A team uses the Full Route Cache to serve a pre-rendered marketing page from disk on every request without running any React code.
  • A client-side Router Cache prevents a round-trip when a user navigates back to a page they visited moments ago in the same session.

More examples

Request Memoization in action

Identical fetch calls within a single server render are deduped by React; no explicit memoisation is needed.

Example · ts
// Both calls resolve to the same in-memory result within one render pass
const user1 = await fetch('/api/user/42').then(r => r.json()); // fetches
const user2 = await fetch('/api/user/42').then(r => r.json()); // deduped
// user1 === user2, only one HTTP request was made

Data Cache with tag for invalidation

The Data Cache persists fetch results across requests; tagging them enables precise invalidation without clearing everything.

Example · ts
// Store in Data Cache with a tag
const res = await fetch('https://api.example.com/posts', {
  next: { tags: ['posts'] },
});

// Later — bust only posts entries
import { revalidateTag } from 'next/cache';
revalidateTag('posts');

Opt out of Full Route Cache

force-dynamic or revalidate=0 prevents Next.js from caching the rendered HTML in the Full Route Cache.

Example · ts
// app/dashboard/page.tsx
export const dynamic = 'force-dynamic';
// or use:
export const revalidate = 0;

export default async function DashboardPage() {
  const data = await fetchLiveData();
  return <main>{data.value}</main>;
}

Discussion

  • Be the first to comment on this lesson.