Learn the concept
Caching & Revalidation
Next.js has multiple cache layers: Request Memoization (dedupes fetch calls per render), Data Cache (persists fetch results across requests), Full Route Cache (caches rendered routes), and Router Cache (client-side cache). Revalidation can be time-based (revalidate option), on-demand (revalidatePath/revalidateTag), or opt-out entirely with cache: 'no-store'.
getUser() in multiple components, only one request madefetch() options: cache, next.revalidate, next.tagsrouter.refresh(), revalidatePath, cookie changes"use cache"Next.js 16 introduces an opt-in caching model. Instead of caching by default, you explicitly opt in with the "use cache" directive:
// Mark a function for caching
async function getProducts() {
'use cache';
cacheTag('products');
cacheLife('hours');
return await db.product.findMany();
}cacheLife ProfilesBuilt-in profiles control cache duration: 'seconds', 'minutes', 'hours', 'days', 'weeks', 'max'.
cacheTag() and revalidateTag()Use cacheTag('name') inside "use cache" functions, then revalidateTag('name', 'max') to invalidate. Note: revalidateTag requires two arguments (tag and scope).
updateTag() for Immediate UpdatesUse updateTag() when you need immediate cache updates without waiting for revalidation.
unstable_cache Deprecationunstable_cache is on a deprecation path. Prefer the "use cache" + cacheTag() pattern for new code.
// No caching - always fresh
fetch(url, { cache: 'no-store' })
// Cache indefinitely (default for GET)
fetch(url, { cache: 'force-cache' })
// Revalidate every 60 seconds
fetch(url, { next: { revalidate: 60 } })
// Tag for on-demand revalidation
fetch(url, { next: { tags: ['posts'] } })revalidatePath('/blog') - Revalidate specific pathrevalidatePath('/blog', 'layout') - Revalidate layout and childrenrevalidateTag('posts', 'max') - Revalidate all fetches with tag (v16 requires two args)// Force dynamic rendering
export const dynamic = 'force-dynamic';
// Force static (error if dynamic functions used)
export const dynamic = 'force-static';
// Revalidate entire route every hour
export const revalidate = 3600;// app/products/page.tsx
// Cached indefinitely (default) - good for static data
async function getCategories() {
const res = await fetch('https://api.example.com/categories');
return res.json();
}
// Time-based revalidation - good for data that changes periodically
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 }, // Revalidate every hour
});
return res.json();
}
// No caching - good for real-time data
async function getInventory(productId: string) {
const res = await fetch(`https://api.example.com/inventory/${productId}`, {
cache: 'no-store', // Always fresh
});
return res.json();
}
// Tagged for on-demand revalidation
async function getProductDetails(id: string) {
const res = await fetch(`https://api.example.com/products/${id}`, {
next: {
tags: [`product-${id}`, 'products'], // Multiple tags
revalidate: 86400, // Also time-based (1 day)
},
});
return res.json();
}Using cacheTag with webhook-triggered revalidateTag to instantly update cached pages when CMS editors publish content
Using cacheLife profiles for product pages (hours) vs inventory (no cache) to balance performance with freshness
Designing cache layers: CDN for static assets, use cache for data, and Router Cache for client-side navigation
Build an app showing use cache with different cacheLife profiles, cacheTag for on-demand revalidation, and monitoring cache behavior
Create a CMS integration that receives webhooks and calls revalidateTag to invalidate specific cached content