One of the most complex advanced questions in a Next.js interview is:

How does Next.js cache data, and what are the four levels of caching in the App Router? How do you configure request memoization, data caching, and dynamic revalidation?

Next.js extends the native JavaScript fetch API to provide server-side caching, request memoization, and automated page revalidation. Understanding how these layers interact is vital for optimizing performance and preventing stale content.


1. The Four Cache Layers#

Next.js uses a multi-layered cache model to minimize database lookups and API calls.

    Client Request
         │
         ▼
 ┌───────────────┐
 │ Request Memo  │  (Renders identical fetches within a single render pass)
 └───────┬───────┘
         │ Cache Miss
         ▼
 ┌───────────────┐
 │ Data Cache    │  (Persists fetched JSON data across requests and server restarts)
 └───────┬───────┘
         │ Cache Miss
         ▼
 ┌───────────────┐
 │ Full Route    │  (HTML & RSC Payload caching for static route segments)
 │ Cache         │
 └───────┬───────┘
         │ Cache Miss
         ▼
 ┌───────────────┐
 │ Router Cache  │  (In-memory client-side cache for layouts and pages during navigation)
 └───────────────┘

2. Deep Dive: The Caching Mechanisms#

A. Request Memoization#

If you call the same API endpoint using the same parameters in multiple Server Components in a single request, Next.js automatically deduplicates the calls.

  • Scope: Single HTTP request life-cycle.
  • Opt-out: Use AbortController or pass { cache: 'no-store' }.
TYPESCRIPT
// Even if this function is called in Header, Footer, and Sidebar, 
// the HTTP request runs ONLY once.
async function getUser() {
  const res = await fetch('https://api.example.com/user');
  return res.json();
}

B. Data Cache#

Next.js stores data fetched from external systems.

  • Scope: Persistent across multiple requests and user sessions.
  • Storage: Flat files in .next/cache or external caches.
  • Opt-out: Pass { cache: 'no-store' } to the fetch options.
TYPESCRIPT
// Fetches and caches indefinitely
const res = await fetch('https://api.example.com/data', { cache: 'force-cache' });

3. Revalidating Cached Data#

To refresh cached data, Next.js provides two types of revalidation (Incremental Static Regeneration):

A. Time-based Revalidation#

Refreshes data after a specific interval.

TYPESCRIPT
// Revalidate this fetch at most every 60 seconds
const res = await fetch('https://api.example.com/posts', {
  next: { revalidate: 60 }
});

B. On-demand Revalidation#

Refreshes data manually via a tags system or path trigger, ideal for updating content immediately when database mutations occur (e.g. headless CMS webhook callbacks).

TYPESCRIPT
// Fetch data and tag it
const res = await fetch('https://api.example.com/products', {
  next: { tags: ['products-list'] }
});

To purge the cache inside a Server Action or Route Handler:

TYPESCRIPT
import { revalidateTag, revalidatePath } from 'next/cache';

async function updateProduct() {
  'use server';
  await saveProductToDb();
  // Trigger on-demand cache clear
  revalidateTag('products-list'); 
}

4. Opting Out of Caching (Dynamic Rendering)#

Next.js automatically statically generates routes unless you explicitly use dynamic functions or opt-out in settings:

  • Dynamic Functions: Accessing cookies(), headers(), or searchParams on pages forces the route to render dynamically on every request.
  • Segment Config: You can define route-level configurations directly:
    TYPESCRIPT
    export const dynamic = 'force-dynamic'; // Equivalent to getServerSideProps
    

Key Takeaways#

  • Deduplication: Request memoization is automatic for fetch calls, preventing duplicate network queries on a single render tree.
  • Data Cache persistence: Data cache is preserved between deployments unless invalidated by revalidation tags.
  • Route revalidation: Use revalidateTag() to purge specific fetch data selectively rather than rebuilding whole page paths.
  • Router Cache: Client-side navigation caches segments in browser memory temporarily (30s for dynamic, 5m for static).