Next.js: Data Fetching, Caching, and Revalidation
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
AbortControlleror pass{ cache: 'no-store' }.
// 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/cacheor external caches. - Opt-out: Pass
{ cache: 'no-store' }to thefetchoptions.
// 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.
// 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).
// 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:
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(), orsearchParamson pages forces the route to render dynamically on every request. - Segment Config: You can define route-level configurations directly:
export const dynamic = 'force-dynamic'; // Equivalent to getServerSideProps
Key Takeaways
- Deduplication: Request memoization is automatic for
fetchcalls, 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).