Caching
Next.js caching has changed significantly across versions. Understanding which version you’re on determines what defaults apply and which APIs are available.
The Big Picture
| Version | fetch default | Cache API | Key feature |
|---|---|---|---|
| v14 | force-cache (aggressive) | revalidate options | Everything cached by default |
| v15 | no-store (no caching) | Same revalidate options | Breaking change - opt into caching |
| v16 | no-store | "use cache" directive | Cache Components, compiler-driven |
Next.js 15: The Caching Reset
Next.js 15 flipped the default. fetch() no longer caches by default. This was the biggest breaking change in the v15 release.
Before (v14) - cached by default
// v14: This fetch is cached indefinitely by default
const data = await fetch("https://api.example.com/posts");
After (v15+) - no caching by default
// v15+: This fetch runs every request (no caching)
const data = await fetch("https://api.example.com/posts");
// Explicitly opt into caching
const data = await fetch("https://api.example.com/posts", {
next: { revalidate: 3600 }, // cache for 1 hour
});
// Or cache indefinitely
const data = await fetch("https://api.example.com/posts", {
cache: "force-cache",
});
Gotcha: If you’re upgrading from v14 to v15+, your app will make many more network requests than before. Audit your
fetchcalls and add explicit caching where needed.
Fetch Caching Options
// No caching (default in v15+)
fetch(url);
fetch(url, { cache: "no-store" });
// Cache until manually revalidated
fetch(url, { cache: "force-cache" });
// Cache with time-based revalidation
fetch(url, { next: { revalidate: 60 } }); // revalidate every 60 seconds
// Cache with tag-based revalidation
fetch(url, { next: { tags: ["posts"] } });
Revalidation
// Time-based: set on the fetch or at the segment level
export const revalidate = 3600; // revalidate this page every hour
// On-demand: trigger from a Server Action or Route Handler
import { revalidateTag, revalidatePath } from "next/cache";
async function publishPost() {
"use server";
await db.post.create({ ... });
revalidateTag("posts"); // revalidate all fetches tagged "posts"
revalidatePath("/blog"); // revalidate a specific path
}
Next.js 16: “use cache” Directive
Next.js 16 introduces a new caching model built on the "use cache" directive. Instead of configuring caching per-fetch, you mark entire components, pages, or functions as cacheable.
"use cache";
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const product = await db.product.findUnique({ where: { id } });
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>${product.price}</p>
</div>
);
}
The compiler analyzes the function’s inputs and generates cache keys automatically. No manual key management.
Granular Caching
// Cache an entire page
"use cache";
export default async function Page() { ... }
// Cache a specific function
async function getProduct(id: string) {
"use cache";
return db.product.findUnique({ where: { id } });
}
// Cache a component
async function ProductCard({ id }: { id: string }) {
"use cache";
const product = await getProduct(id);
return <div>{product.name}</div>;
}
cacheLife Profiles
Control how long cached values live:
import { cacheLife } from "next/cache";
async function getProducts() {
"use cache";
cacheLife("hours"); // built-in profile
return db.product.findMany();
}
Built-in profiles:
| Profile | Stale | Revalidate | Expire |
|---|---|---|---|
"default" | undefined | 15 min | undefined |
"seconds" | 0 | 1 sec | 60 sec |
"minutes" | 5 min | 1 min | 1 hour |
"hours" | 5 min | 1 hour | 1 day |
"days" | 5 min | 1 day | 1 week |
"weeks" | 5 min | 1 week | 1 month |
"max" | 5 min | 1 month | indefinite |
Custom profiles in next.config.ts:
// next.config.ts
const nextConfig = {
cacheLife: {
catalog: {
stale: 300, // 5 min client cache
revalidate: 900, // 15 min server revalidate
expire: 86400, // 24 hour max age
},
},
};
export default nextConfig;
Cache Hierarchy
Requests flow through multiple cache layers:
Client Request
-> Router Cache (client-side, in-memory)
-> Full Route Cache (server-side, rendered pages)
-> Data Cache (server-side, fetch results)
-> Data Source (origin)
| Layer | Where | What | Duration |
|---|---|---|---|
| Router Cache | Client | Prefetched route segments | Session (resets on refresh) |
| Full Route Cache | Server | Pre-rendered HTML + RSC payload | Until revalidated |
| Data Cache | Server | fetch responses | Until revalidated |
Tip: When debugging caching issues, work backwards: is it the Router Cache (client)? The Full Route Cache (server)? Or the Data Cache (fetch)? Each has different invalidation strategies.
Opting Out of Caching
// Per-fetch
fetch(url, { cache: "no-store" });
// Per-page/layout (force dynamic rendering)
export const dynamic = "force-dynamic";
// Using dynamic functions (automatically opts out)
import { cookies, headers } from "next/headers";
const cookieStore = await cookies(); // makes the route dynamic
Next: Server Components | Cache Components