Caching Architecture
Priority: P1 (HIGH)
Next.js has 4 distinct caching layers. Understanding them prevents stale data bugs.
The 4 Layers
| Layer | Where | Duration | Purpose | Control |
|---|---|---|---|---|
| 1. Request Memoization | Server | Per Request | Deduplicate same fetch() calls in one render pass. |
AbortController |
| 2. Data Cache | Server | Persistent | Store data across user requests. | revalidateTag |
| 3. Full Route Cache | Server | Persistent | Store HTML/RSC payload. (Static Rendering). | revalidatePath |
| 4. Router Cache | Client | Session (< 5m) | Navigating back/forward without server hit. | router.refresh() |
1. Request Memoization
Behavior: Calling
getUser(1)inlayout,page, andcomponentonly triggers 1 network call.Key: Matches URL and Options exactly.
Non-Fetch: For DB calls/Prisma, use React
cache()manually.import { cache } from 'react'; const getItem = cache(async (id) => db.item.findUnique({ id }));
2. Data Cache (The "Server Persistence")
Default:
fetchtracks cache indefinitely (force-cache).Scaling:
- Vercel: Shared globally via KV/Blob.
- Self-Hosted: Stored in filesystem (Pod ephemeral storage). Critical: Must configure a shared
cacheHandler(Redis) for multi-pod Kubernetes setups, otherwise pods have desynchronized caches.
Granular Control: Use
unstable_cachefor DB queries caching.import { unstable_cache } from 'next/cache'; const getCachedUser = unstable_cache( async (id) => db.user.find(id), ['users-key'], { tags: ['users'], revalidate: 60 }, );
3. Router Cache (The "Client Stale")
- Problem: User updates a listing, navigates back, and sees old data.
- Cause: Client-side Router Cache holds payload for 30s (dynamic) or 5m (static).
- Fix: Call
router.refresh()in a Client Component orrevalidatePath()in a Server Action (which automatically invalidates Router Cache).
Strategies
- Opt-Out (Real-Time):
- Data:
fetch(..., { cache: 'no-store' }) - Route:
export const dynamic = 'force-dynamic'
- Data:
- On-Demand (CMS/Admin):
- Use
revalidateTag('collection')for surgical updates. Better thanrevalidatePath(which nukes the whole URL).
- Use