Skip to main content

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​

VersionChange
14fetch and GET Route Handlers are cached by default; the client Router Cache keeps dynamic pages for 30 seconds
15fetch and GET Route Handlers are not cached by default; the Router Cache no longer reuses dynamic pages (staleTimes.dynamic: 0)
16Cache Components behind a flag ('use cache', cacheLife, cacheTag); new updateTag and refresh; revalidateTag takes a second argument
16.4Cache Components recommended for all apps and enabled in new create-next-app projects

Where Data Is Cached​

LayerLifetimeWhat is stored
One requestA single server renderResults of identical calls, so a layout and a page share one request
Server dataBetween requestsResults of data functions
Prerendered routeUntil revalidation or a new buildHTML and RSC Payload made at build time
BrowserThe session, in memoryRSC Payload of visited and prefetched segments

Enabled with cacheComponents: true and partialPrefetching: true in next.config.

  • One request: fetch is memoized; other functions are wrapped in React cache.
  • 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 stale time of cacheLife.

Rules:

  • Uncached async work and runtime APIs (cookies(), headers(), searchParams, params not listed in generateStaticParams) 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):

Profilestalerevalidateexpire
default5m15mnever
seconds30s1s60s
minutes5m1m1h
hours5m1h1d
days5m1d1w
weeks5m1w30d
max5m30d1y

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.

FunctionWhereEffect
revalidateTag(tag, 'max')Server Actions, Route HandlersStale-while-revalidate: the old data is served while the new is fetched. The one-argument form is deprecated
updateTag(tag)Server Actions onlyExpires immediately: the user sees their own change after the action
revalidatePath(path)Server Actions, Route HandlersInvalidates everything cached for a path
refresh()Server Actions onlyRe-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.

app/posts/page.tsx
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>
);
}
app/posts/actions.ts
'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');
}

Follow-up Questions​

  • Is fetch cached by default? Not since Next.js 15. With Cache Components nothing is cached without 'use cache'.
  • What is the difference between revalidateTag and updateTag? revalidateTag serves stale data while refreshing in the background; updateTag expires at once and is meant for read-your-own-writes in a Server Action.
  • How does React cache differ from 'use cache'? React cache deduplicates 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 = 60 with Cache Components? A build error: dynamic, dynamicParams, revalidate and fetchCache are not compatible with Cache Components; lifetimes come from cacheLife.
  • Why pair cacheTag with a long cacheLife? 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> or loading.tsx above it: the build fails with a blocking-route error. Fixes: a Suspense boundary, 'use cache', or export const instant = false to 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() or searchParams to fetch fresh data on every request: it was prerendered at build time.

See Also​