Caching Deep Dive
The App Router caches aggressively, by default, at four distinct layers — and each layer caches a different thing, for a different reason, invalidated by different triggers. Understanding which layer is responsible for a piece of stale data you're staring at is most of the battle when debugging "why isn't this updating?".
The four mechanisms, at a glance
Mechanism | What is cached | Where | How long |
|---|---|---|---|
Request Memoization | The return value of an identical | Server, per-request only | Duration of a single render pass — cleared once the request finishes |
Data Cache | The return value of | Server, persists across requests and deployments | Indefinite by default, or a |
Full Route Cache | The rendered HTML + RSC payload for a statically-rendered route | Server (typically at the CDN/edge) | Indefinite for static routes — until a new deployment or |
Router Cache | RSC payloads for routes the user has visited or prefetched, on the client | Browser, per session | A short window (session-based); cleared on full page reload or after a Server Action's |
Request Memoization
fetch URL with the same options is called multiple times while rendering one page — say, both a layout and a page each call the same "get current user" function — React memoizes the result so only the first call actually hits the network. This is scoped to a single request only; it never persists between separate visitors or reloads.Two components, one fetch, during a single render
// layout.tsx and page.tsx both call this during the same request.
// The second call reuses the first call's in-flight/completed result.
async function getUser() {
const res = await fetch('https://api.example.com/me')
return res.json()
}Data Cache
fetch results across separate requests and even across deployments, on the server. By default a fetch call is cached indefinitely; you opt into time-based revalidation, or opt out of caching entirely, per call.Controlling the Data Cache per fetch call
// Cached indefinitely (default)
await fetch('https://api.example.com/posts')
// Revalidate at most once every 60 seconds
await fetch('https://api.example.com/posts', { next: { revalidate: 60 } })
// Never cache — always hit the network
await fetch('https://api.example.com/posts', { cache: 'no-store' })Full Route Cache
revalidatePath/revalidateTag from a Server Action or Route Handler.Invalidating the Full Route Cache after a mutation
'use server'
import { revalidatePath } from 'next/cache'
export async function publishPost(id: string) {
await db.post.update({ where: { id }, data: { published: true } })
revalidatePath('/blog')
}Router Cache
revalidatePath/revalidateTag call from the server, which invalidates the corresponding client-side entries too.Request Memoization — one render pass, dedupes identical
fetchcalls within it.Data Cache — server, persists indefinitely by default, controlled per
fetchwithrevalidate/no-store.Full Route Cache — server, caches a static route's rendered output until a deploy or manual revalidation.
Router Cache — browser, caches visited/prefetched route payloads for a session.
revalidatePath/revalidateTagare the shared tools for clearing the Data Cache and Full Route Cache (and, by extension, the Router Cache entries built from them) on demand.