# Next.js 15 Caching Deep Dive ## The Four Caching Layers ### 1. Request Memoization (in-memory, per request) Automatically deduplicates identical `fetch()` calls within a single render tree. ```tsx // Both components call fetchUser(id) — only ONE network request is made async function Avatar({ id }) { const u = await fetchUser(id); return } async function Name({ id }) { const u = await fetchUser(id); return {u.name} } ``` ### 2. Data Cache (persistent, cross-request) `fetch()` responses are stored on the server and reused across requests and deployments. ```tsx // Static — cached indefinitely fetch(url) fetch(url, { cache: "force-cache" }) // Time-based revalidation (ISR) fetch(url, { next: { revalidate: 3600 } }) // revalidate every hour // Always fresh — no caching fetch(url, { cache: "no-store" }) fetch(url, { next: { revalidate: 0 } }) ``` ### 3. Full Route Cache (build-time static rendering) Pages rendered at build time are stored as static HTML+RSC payload. ```tsx // Force dynamic rendering for this route export const dynamic = "force-dynamic" // Custom revalidation period for the whole route export const revalidate = 60 ``` ### 4. Router Cache (client-side, per session) Browser caches RSC payloads for instant back/forward navigation. - Static routes: cached for 5 minutes - Dynamic routes: cached for 30 seconds ## On-Demand Revalidation ```ts // Revalidate by path import { revalidatePath } from "next/cache" revalidatePath("/blog") revalidatePath("/blog/[slug]", "page") // Revalidate by cache tag import { revalidateTag } from "next/cache" revalidateTag("posts") // Tagging fetches fetch(url, { next: { tags: ["posts"] } }) ``` ## unstable_cache for Non-Fetch Data ```ts import { unstable_cache } from "next/cache" const getCachedUser = unstable_cache( async (id: string) => db.user.findUnique({ where: { id } }), ["user"], { revalidate: 300, tags: ["users"] } ) ``` ## Common Pitfalls | Mistake | Fix | |---------|-----| | Using `fetch` inside `useEffect` | Move to Server Component or use React Query | | Forgetting `revalidatePath` after mutations | Call in Server Action after every write | | Over-caching user-specific data | Add `cache: "no-store"` or check `cookies()`/`headers()` | | Sequential awaits | Use `Promise.all()` for parallel fetching |