The most misdiagnosed staleness bug in Next.js: prefetch entry types (full vs partial), staleTimes per entry type, why Server Actions + revalidatePath do not immediately update what the user sees, router.refresh() semantics vs router.push(), and the production debugging workflow for "I mutated data and the UI is stale."
A-16 — The Router Cache Deep Dive
Who this is for: Engineers who have hit the classic incident and want to understand it completely before it hits them in production again.
You call revalidatePath('/dashboard') in a Server Action after updating a user's subscription tier. The action returns success. The user is still on the page. Their subscription badge still shows "Free". You check the database — the update landed. You check the server logs — the cache was invalidated. The browser still shows the wrong tier.
This is the Router Cache.
What the Router Cache Actually Is
The Router Cache is a client-side, in-memory store that lives inside the React Router's internal state. It is not localStorage. It is not the browser's HTTP cache. It is not a Service Worker cache. It is React state that is allocated when the Next.js router initialises and deallocated on full page reload.
The Router Cache stores RSC payloads — React Flight Protocol data, the binary format that Server Components use to send their rendered output to the client. Each entry is keyed by pathname plus serialised search parameters.
The Router Cache is entirely separate from Next.js's three server-side caches:
| Cache | Location | Scope | Survives page reload? |
|---|---|---|---|
| Router Cache | Client RAM | Per-tab | No |
| Data Cache | Server (filesystem/Redis) | Across all requests | Yes |
| Full Route Cache | Server (filesystem) | Across all requests | Yes |
| Request Memoization | Server RAM | Per-request | N/A |
Confusion between these four caches causes the majority of Next.js caching bugs. The Router Cache is the one engineers know least about because it's invisible in DevTools until you know where to look. The Data Cache and Full Route Cache have extensive documentation. The Router Cache is where bugs actually happen in production.
When revalidatePath runs on the server, it invalidates the Data Cache and Full Route Cache entries for that path. It has no mechanism to reach across the network and invalidate the Router Cache in a user's browser. The server doesn't even know which users have that path cached. The invalidation signal stops at the server boundary.
How Router Cache Entries Get Populated
Three paths into the Router Cache:
1. Prefetch on hover or viewport entry
When a <Link> component enters the viewport or the user hovers over it, Next.js fires a prefetch request for the route. The response is stored in the Router Cache tagged as a prefetch entry.
What gets stored depends on whether the route is static or dynamic:
- Static route: The full RSC payload is stored. Layout, page, all data.
- Dynamic route: Only the static portion is stored. Specifically: the layout segment RSC. The dynamic page content (the parts that vary per request) is not prefetched. Next.js knows not to cache dynamic content it hasn't fetched with real request context.
This is important: on navigation to a dynamic route, even if there's a Router Cache entry, the page portion will be fetched fresh. The layout portion is served from cache.
2. Navigation
When you navigate to a route (via <Link> click, router.push(), router.replace()), the full RSC payload is fetched and stored as a "full" entry. This entry is distinct from a prefetch entry — it includes the complete page content.
3. Explicit prefetch
router.prefetch('/path') programmatically stores a prefetch entry. Useful for warming the cache before the user interacts with a <Link> that isn't in the viewport.
Entry Types and Default Staleness
| Entry type | What's stored | Default staleTime |
|---|---|---|
| Static route prefetch | Full RSC payload | 5 minutes |
| Dynamic route prefetch | Layout RSC only | 30 seconds |
| Full navigation (dynamic route) | Full RSC payload | 30 seconds |
| Full navigation (static route) | Full RSC payload | 5 minutes |
"Stale" means the entry is expired and Next.js will refetch on the next navigation. An entry being stale does not cause an immediate refetch — it causes a refetch the next time that route is navigated to.
staleTimes Configuration
You can override the default TTLs globally:
Setting dynamic: 0 disables the Router Cache for dynamic routes entirely. Every navigation to a dynamic route refetches the RSC payload from the server, bypassing the cache. Use this for applications where data freshness is non-negotiable: trading dashboards, live auction pages, real-time collaboration tools.
The cost: navigation feels slightly slower. With dynamic: 0, navigating between pages always requires a round-trip to the server before the new content renders. With caching, navigation is instant if a warm entry exists.
Setting dynamic: 0 does not affect static routes. Those continue to use the static TTL.
Why revalidatePath Doesn't Update What the User Sees
This is the incident from the module introduction. Walk through it step by step:
-
User loads
/dashboard. Next.js fetches the RSC payload. The Router Cache stores a full entry. The user sees their current subscription tier. -
User clicks "Upgrade to Pro". A Server Action runs:
typescript
Sign in to keep reading
The rest of this module is free — sign in with Google to unlock it and track your progress.
Sign in & RegisterDiscussion
0Join the discussion