# Next.js Caching Architecture

> The 4 layers of caching (Memoization, Data Cache, Full Route, Router Cache).

- Skill: `ngxtm-devkit/next-js-caching-architecture` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add ngxtm-devkit/next-js-caching-architecture`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ngxtm-devkit/next-js-caching-architecture/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: ngxtm (https://skillmd.com/u/ngxtm-devkit)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ngxtm-devkit/next-js-caching-architecture

---


# 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)` in `layout`, `page`, and `component` only triggers 1 network call.
- **Key**: Matches URL and Options exactly.
- **Non-Fetch**: For DB calls/Prisma, use React `cache()` manually.

  ```tsx
  import { cache } from 'react';
  const getItem = cache(async (id) => db.item.findUnique({ id }));
  ```

## 2. Data Cache (The "Server Persistence")

- **Default**: `fetch` tracks 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_cache` for DB queries caching.

  ```tsx
  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 or `revalidatePath()` 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'`
- **On-Demand (CMS/Admin)**:
  - Use `revalidateTag('collection')` for surgical updates. Better than `revalidatePath` (which nukes the whole URL).

