Caching
Caching in Next.js is described by four questions: what is cached, where
(one request, the server, the prerendered route, the browser), for how long
and how it is invalidated. The App Router has two caching models, chosen by
the cacheComponents flag. With Cache Components (on in new projects since
16.4, the default from 17) nothing is cached implicitly: you mark a function
or a component with 'use cache' and set its lifetime with cacheLife.
In the previous model caching is set with fetch options and route segment
config, and since Next.js 15 fetch is not cached by default.
Applies to: Next.js 16.4.
Details
What Changed Between Versions
| Version | Change |
|---|---|
| 14 | fetch and GET Route Handlers are cached by default; the client Router Cache keeps dynamic pages for 30 seconds |
| 15 | fetch and GET Route Handlers are not cached by default; the Router Cache no longer reuses dynamic pages (staleTimes.dynamic: 0) |
| 16 | Cache Components behind a flag ('use cache', cacheLife, cacheTag); new updateTag and refresh; revalidateTag takes a second argument |
| 16.4 | Cache Components recommended for all apps and enabled in new create-next-app projects |
Where Data Is Cached
| Layer | Lifetime | What is stored |
|---|---|---|
| One request | A single server render | Results of identical calls, so a layout and a page share one request |
| Server data | Between requests | Results of data functions |
| Prerendered route | Until revalidation or a new build | HTML and RSC Payload made at build time |
| Browser | The session, in memory | RSC Payload of visited and prefetched segments |
- Cache Components
- Previous model
Enabled with cacheComponents: true and partialPrefetching: true in
next.config.
- One request:
fetchis memoized; other functions are wrapped in Reactcache. - Server data: the result of a
'use cache'function or component, kept in the memory of the instance;'use cache: remote'stores it in a shared cache handler. - Prerendered route: a static shell of static markup, cached parts and
<Suspense>fallbacks; the rest streams on request (Partial Prerendering). - Browser: the same RSC Payload; how long it is fresh is set by the
staletime ofcacheLife.
Rules:
- Uncached async work and runtime APIs (
cookies(),headers(),searchParams,paramsnot listed ingenerateStaticParams) must be inside<Suspense>or cached; otherwise Next.js reports a blocking route in development and fails the build. 'use cache'goes at the top of a file, a function or a component. The arguments and the captured variables are the cache key; arguments and the result must be serializable.- Runtime APIs cannot be called inside
'use cache': read the value outside and pass it as an argument.'use cache: private'allows them but caches only in the browser.
cacheLife profiles (stale is for the browser, revalidate is when the
server refreshes in the background, expire is when a request has to wait for
fresh data):
| Profile | stale | revalidate | expire |
|---|---|---|---|
default | 5m | 15m | never |
seconds | 30s | 1s | 60s |
minutes | 5m | 1m | 1h |
hours | 5m | 1h | 1d |
days | 5m | 1d | 1w |
weeks | 5m | 1w | 30d |
max | 5m | 30d | 1y |
Used when cacheComponents is off. The layers have their own names:
- Request Memoization: identical
fetchcalls in one render; other functions are wrapped in Reactcache. - Data Cache:
fetchresults withcache: 'force-cache'ornext.revalidate, and functions wrapped inunstable_cache. - Full Route Cache: HTML and RSC Payload of a route that is entirely static; a route is either static or dynamic as a whole.
- Router Cache: in the browser; since 15 dynamic pages are fetched again on every navigation.
| Option | Effect |
|---|---|
fetch(url, { cache: 'force-cache' }) | Store in the Data Cache |
fetch(url, { next: { revalidate: 60 } }) | Store and refresh at most every 60 seconds |
fetch(url, { next: { tags: ['posts'] } }) | Tag for revalidateTag and updateTag |
unstable_cache(fn, keys, { tags, revalidate }) | Cache a non-fetch function, such as a database query |
export const dynamic = 'force-dynamic' | Render the route on every request |
export const revalidate = 60 | Default revalidation for the route segment |
A route becomes dynamic when it uses cookies(), headers(), searchParams,
connection() or cache: 'no-store'. A route without them is still
prerendered at build time, together with the result of its uncached fetch.
On-Demand Revalidation
The same in both models. All functions are imported from next/cache and are
synchronous. Tags come from cacheTag() or from fetch next.tags.
| Function | Where | Effect |
|---|---|---|
revalidateTag(tag, 'max') | Server Actions, Route Handlers | Stale-while-revalidate: the old data is served while the new is fetched. The one-argument form is deprecated |
updateTag(tag) | Server Actions only | Expires immediately: the user sees their own change after the action |
revalidatePath(path) | Server Actions, Route Handlers | Invalidates everything cached for a path |
refresh() | Server Actions only | Re-renders the current page with fresh uncached data, without touching the cache |
Example
Posts are cached for an hour and tagged; the action expires the tag so the author sees the new post at once.
- Cache Components
- Previous model
import { cacheLife, cacheTag } from 'next/cache';
import { db } from '@/lib/db';
async function getPosts() {
'use cache';
cacheLife('hours');
cacheTag('posts');
return db.post.findMany();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
'use server';
import { updateTag } from 'next/cache';
import { db } from '@/lib/db';
export async function createPost(formData: FormData) {
await db.post.create({ data: { title: String(formData.get('title')) } });
updateTag('posts');
}
type Post = { id: number; title: string };
export default async function PostsPage() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600, tags: ['posts'] },
});
const posts: Post[] = await res.json();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
'use server';
import { updateTag } from 'next/cache';
export async function createPost(formData: FormData) {
await fetch('https://api.example.com/posts', {
method: 'POST',
body: formData,
});
updateTag('posts');
}
Follow-up Questions
- Is
fetchcached by default? Not since Next.js 15. With Cache Components nothing is cached without'use cache'. - What is the difference between
revalidateTagandupdateTag?revalidateTagserves stale data while refreshing in the background;updateTagexpires at once and is meant for read-your-own-writes in a Server Action. - How does React
cachediffer from'use cache'? Reactcachededuplicates calls within one render;'use cache'keeps the result between requests. - Why can
'use cache'data disappear on serverless hosting? By default it is kept in the memory of one instance;'use cache: remote'is needed for a shared cache. - What happens to
export const revalidate = 60with Cache Components? A build error:dynamic,dynamicParams,revalidateandfetchCacheare not compatible with Cache Components; lifetimes come fromcacheLife. - Why pair
cacheTagwith a longcacheLife? Content that changes only when you write it is cached for a long time and expired on demand by its tag; content that changes on its own gets a short profile instead.
Common Mistakes
- Describing the Next.js 14 model ("everything is cached") as current.
- With Cache Components, awaiting
params,cookies()or uncached data at the top of a page with no<Suspense>orloading.tsxabove it: the build fails with a blocking-route error. Fixes: a Suspense boundary,'use cache', orexport const instant = falseto allow a blocking route. - Calling
cookies()inside a'use cache'function instead of passing the value as an argument. - In the previous model, expecting a page without
cookies()orsearchParamsto fetch fresh data on every request: it was prerendered at build time.