Next.js Caching Architecture & Revalidation Strategies
Caching is one of the most powerful and sophisticated aspects of Next.js. Next.js employs a 4-tier caching architecture spanning the server and client to minimize expensive database queries, eliminate duplicate network requests, and serve pre-rendered pages with sub-millisecond latency.
In this lesson, you will master the 4 cache layers, time-based revalidation, on-demand revalidation via revalidateTag and revalidatePath, and cache purge strategies.
┌─────────────────────────────────────────────────────────────────────────────┐
│ The 4 Next.js Caching Layers │
├───────────────────┬──────────────┬────────────────────────┬─────────────────┤
│ Layer │ Where │ Purpose │ Duration │
├───────────────────┼──────────────┼────────────────────────┼─────────────────┤
│ 1. Request Memo │ Server │ Deduplicates functions │ Per Request │
│ 2. Data Cache │ Server │ Stores data across reqs│ Persistent │
│ 3. Full Route │ Server │ Stores HTML & RSC Tree │ Persistent/ISR │
│ 4. Router Cache │ Client (Web) │ Instant Link transition│ Session/In-Mem │
└───────────────────┴──────────────┴────────────────────────┴─────────────────┘
1. Time-Based Revalidation
Automatically refresh cached data after a specific duration (in seconds):
// Fetches fresh data at most once every 60 seconds (1 minute)
export async function getLiveStockPrices() {
const res = await fetch('https://api.marketdata.com/stocks', {
next: { revalidate: 60 },
});
return res.json();
}
2. On-Demand Revalidation with Cache Tags (revalidateTag)
Assign cache tags to fetch() requests and invalidate them instantly when data is mutated:
A. Assigning Cache Tags:
// lib/data/products.ts
export async function getProductCatalog() {
const res = await fetch('https://api.store.com/products', {
next: { tags: ['products', 'inventory'] },
});
return res.json();
}
B. Purging the Cache in a Server Action:
// app/actions/products.ts
'use server';
import { revalidateTag, revalidatePath } from 'next/cache';
export async function updateProductPrice(productId: string, newPrice: number) {
await db.product.update({
where: { id: productId },
data: { price: newPrice },
});
// Purges ALL cached queries tagged with 'products' across the entire server!
revalidateTag('products');
// Also purge the specific product URL page
revalidatePath(`/products/${productId}`);
}
3. Opting Out of Caching (no-store)
When data must always reflect real-time live database state (e.g. user notifications, live bids, bank account balances):
// Never caches: Executes fresh request every single time
const res = await fetch('https://api.bank.com/balance', {
cache: 'no-store',
});
Summary & Key Takeaways
- Next.js caching consists of 4 tiers: Request Memoization, Data Cache, Full Route Cache, and Client Router Cache.
- Time-based revalidation (
next: { revalidate: 60 }) keeps data fresh on a schedule. - On-demand revalidation via
revalidateTag('tag')updates caches instantly upon database mutations. - Use
cache: 'no-store'orexport const dynamic = 'force-dynamic'for real-time live data.
Best Practices & Senior Guidance
- Prefer
revalidateTagOverrevalidatePath:revalidateTagpurges data across all components and pages referencing that tag, rather than forcing a reload of only a single URL path. - Tag Granularly: Assign both collection tags (
tags: ['products']) and item-specific tags (tags: [product-${id}]) for surgical cache invalidation.