Understanding the Next.js 15 Caching Model
Next.js 15 made caching explicit by default. Understanding the four caching layers is essential for building apps that are both fast and up-to-date.
The Four Caching Layers
- Request Memoization — Deduplicates identical
fetch()calls within a single render pass - Data Cache — Persists fetch results across requests (the
cacheoption controls this) - Full Route Cache — Caches the rendered HTML of static routes on the server
- Router Cache — Client-side cache of visited route segments
Controlling the Data Cache
// No cache — always fetch fresh (Next.js 15 default)
const data = await fetch('/api/posts');
const data = await fetch('/api/posts', { cache: 'no-store' });
// Cache indefinitely (until manually invalidated)
const data = await fetch('/api/posts', { cache: 'force-cache' });
// Cache with time-based revalidation (in seconds)
const data = await fetch('/api/posts', {
next: { revalidate: 60 }, // Revalidate every 60 seconds
});
// Cache with tag-based revalidation
const data = await fetch('/api/posts', {
next: { tags: ['posts'] },
});
Route-Level Cache Control
Set caching for an entire route segment:
// app/blog/page.tsx
// Opt the entire route into dynamic rendering (no caching)
export const dynamic = 'force-dynamic';
// Or set a revalidation interval for the whole page
export const revalidate = 3600; // seconds
On-Demand Revalidation
Purge cached data when it changes using revalidatePath and revalidateTag:
// app/actions/posts.ts
'use server';
import { revalidatePath, revalidateTag } from 'next/cache';
export async function publishPost(postId: string) {
await db.post.update({
where: { id: postId },
data: { published: true },
});
// Clear the blog page cache
revalidatePath('/blog');
// Clear all fetches tagged with 'posts'
revalidateTag('posts');
}
Request Memoization in Practice
// Both components call getUser() but the fetch happens only once per request
// Next.js deduplicates identical fetch calls automatically
async function Header() {
const user = await getUser(); // Fetch #1
return <nav>{user.name}</nav>;
}
async function Sidebar() {
const user = await getUser(); // Deduped — no extra network request
return <aside>{user.email}</aside>;
}
This lets you fetch data exactly where you need it without worrying about over-fetching.