- Next.js
- Caching
- React
- Performance
- Web Development
Next.js 16 Caching Explained: use cache, cacheLife and updateTag vs revalidateTag
In Next.js 16, caching is explicit: nothing is cached until you mark it with the 'use cache' directive. You set how long it lives with cacheLife, label it with cacheTag, and invalidate it with updateTag (immediately) or revalidateTag (in the background).
What changed in Next.js 16 caching?
Earlier versions of the App Router cached a lot implicitly, which made it hard to know why a page showed stale data. Next.js 16 flips the default with Cache Components:
| Before (Next.js 14–15) | Next.js 16 with Cache Components | |
|---|---|---|
| Default | Many fetches and routes cached implicitly | Nothing cached unless you say so |
| How to cache | fetch options, export const revalidate | 'use cache' + cacheLife |
| Invalidate | revalidateTag(tag), revalidatePath | updateTag, revalidateTag(tag, profile), revalidatePath |
| Static + dynamic in one page | Hard | Partial Prerendering by default |
Turn it on in next.config.ts (new apps already have it):
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;
How do you use the 'use cache' directive?
Put 'use cache' at the top of a function, a component or a whole file. The function's arguments become part of the cache key, so getProducts("shoes") and getProducts("bags") are cached separately.
import { cacheLife, cacheTag } from "next/cache";
export async function getProducts(category: string) {
"use cache";
cacheLife("hours"); // how long it stays fresh
cacheTag("products"); // a label to invalidate it later
return db.product.findMany({ where: { category } });
}
A cached component works the same way:
export async function Footer() {
"use cache";
const links = await getFooterLinks();
return <footer>{/* … */}</footer>;
}
| Placement | What gets cached |
|---|---|
| Top of a file | Every export (they must all be async) |
| Inside a component | Its rendered output |
| Inside a function | Its return value |
What do the cacheLife profiles mean?
Each profile sets three times. stale is how long the browser may reuse data without asking; revalidate is when the server starts refreshing in the background; expire is when old data can no longer be served at all.
| Profile | stale | revalidate | expire |
|---|---|---|---|
"seconds" | 30 s | 1 s | 1 min |
"minutes" | 5 min | 1 min | 1 h |
"hours" | 5 min | 1 h | 1 day |
"days" | 5 min | 1 day | 1 week |
"weeks" | 5 min | 1 week | 30 days |
"max" | 5 min | 30 days | 1 year |
You can also pass numbers in seconds, for example cacheLife({ revalidate: 600 }), or define named profiles under cacheLife in next.config.ts.
updateTag vs revalidateTag: which one should you use?
Both invalidate data you labelled with cacheTag. The difference is when users see fresh data.
updateTag("posts") | revalidateTag("posts", "max") | |
|---|---|---|
| Effect | Expires immediately; next read waits for fresh data | Marks stale; old data served while fresh data loads |
| Called from | Server Actions only | Server Actions and route handlers |
| Use for | Read-your-own-writes: forms, settings, "I just posted this" | CMS webhooks, background jobs, data others can see a moment later |
"use server";
import { updateTag } from "next/cache";
export async function createPost(formData: FormData) {
await db.post.create({ data: { title: String(formData.get("title")) } });
updateTag("posts"); // the author sees the new post right away
}
Two related helpers: revalidatePath("/blog") invalidates everything for a route, and refresh() (Server Actions only) re-renders the client router without touching cached data.
How does Partial Prerendering fit in?
With Cache Components, one page can be both static and dynamic. Cached parts are prerendered into a static shell that is served instantly, and parts that read request data (cookies(), headers(), searchParams) stream in at request time. Those dynamic parts must sit inside a <Suspense> boundary:
import { Suspense } from "react";
import { cookies } from "next/headers";
async function Cart() {
const id = (await cookies()).get("cartId")?.value; // request-time data
return <CartList items={await getCart(id)} />;
}
export default function ProductPage() {
return (
<>
<ProductInfo /> {/* 'use cache' → static shell */}
<Suspense fallback={<CartSkeleton />}>
<Cart /> {/* streams in */}
</Suspense>
</>
);
}
What are the common caching mistakes in Next.js 16?
- Reading cookies inside
'use cache'. A shared cache cannot depend on one user. Read the cookie outside and pass the value as an argument, or use'use cache: private'. - Keeping old segment config.
export const revalidate,dynamic,fetchCacheanddynamicParamserror when Cache Components is on. - Using
revalidateTagwhereupdateTagis needed. If the user who made the change sees old data after submitting a form, switch toupdateTag. - Request-time data outside Suspense. Wrap components that read cookies, headers or search params in
<Suspense>so the rest of the page can still be prerendered.
Key takeaway
Cache on purpose. Add 'use cache' where data is shared and changes rarely, pick a cacheLife profile that matches how fresh it must be, tag it, and invalidate with updateTag for the person who just changed it or revalidateTag(tag, "max") for everyone else. For a one-page summary of the rest of Next.js 16, see the free Next.js handwritten notes.
Frequently asked questions
Is fetch cached by default in Next.js 16?
No. In Next.js 16 with Cache Components, fetch requests and database calls are not cached by default. You opt in by adding the 'use cache' directive to a function, a component or a whole file, and you control the lifetime with cacheLife.
What is the difference between updateTag and revalidateTag in Next.js 16?
updateTag expires a cache tag immediately, so the next request waits for fresh data; it can only be called from Server Actions and is meant for read-your-own-writes, such as showing a user the post they just created. revalidateTag(tag, 'max') marks the tag stale and serves the old data while fresh data loads in the background, which suits webhooks and background updates.
How do I enable Cache Components in Next.js 16?
Set cacheComponents: true in next.config.ts. New projects created with create-next-app in Next.js 16 have it enabled already.
Can I use cookies() or headers() inside 'use cache'?
No. A shared cache cannot depend on one user's request. Read cookies() or headers() outside the cached function and pass the value in as an argument, or use the 'use cache: private' variant, which caches per user in the browser.
Do export const revalidate and export const dynamic still work in Next.js 16?
Not when Cache Components is enabled. The route segment options dynamic, revalidate, fetchCache and dynamicParams cause an error with cacheComponents on; use 'use cache' with cacheLife and Suspense boundaries instead. They still work in apps that have not enabled Cache Components.