Caching, revalidation and Cache Components

The caching layers, when fetch is not cached, caching a whole computation with use cache, cacheLife and cacheTag, and invalidating on demand.

What is cached, and when

// 1. request memoisation: the same call in one render pass runs once
async function getGuide(slug) {
  const res = await fetch('https://api.example.com/guides/' + slug)
  return res.json()
}
// called by page.tsx and again by generateMetadata -> one network request

// 2. opt a fetch into the data cache explicitly
const feed = await fetch(url, { next: { revalidate: 60, tags: ['guides'] } })

// 3. cache a computation that is not a fetch at all
import { unstable_cache } from 'next/cache'

const getGuides = unstable_cache(() => db.guide.findMany(), ['guides'], { revalidate: 300 })
  • Four layers stack: memoisation within one render, the data cache across requests, the full route cache for a static page, and the client-side router cache for back and forward navigation.
  • Current releases do not cache fetch by default. Caching is a decision you make per call, not an inherited default — assume fresh unless you said otherwise.
  • A page that reads cookies, headers or search parameters is dynamic, which bypasses the full route cache while still using the data cache of its fetches.
  • Memoisation is why calling the same loader from the page and from generateMetadata is free: it only applies inside a single request.
  • The router cache is why going back feels instant — React reuses the tree it already rendered, for a short window.

use cache, cacheLife and cacheTag

// file-level: this module becomes cacheable
'use cache'

import { cacheLife, cacheTag } from 'next/cache'
import { db } from '@/lib/db'

export async function GuideList() {
  cacheLife('hours')           // a named profile: seconds, minutes, hours, days, weeks
  cacheTag('guides')           // tag it so a mutation can invalidate exactly this

  const guides = await db.guide.findMany()
  return <ul>{guides.map(g => <li key={g.id}>{g.title}</li>)}</ul>
}
  • 'use cache' caches the result of a function or a whole route segment rather than a single fetch, which lets you cache a database query or a rendered component.
  • cacheLife describes how long a value stays fresh, when it refreshes in the background and when it expires — one profile instead of three separate numbers.
  • cacheTag attaches a name to the entry. One tag can cover a function, a route, or many of both, which is what makes precise invalidation possible.
  • A cached function must be deterministic on its inputs. Anything read from the request is not part of the key, so it does not belong inside the cached scope.
  • Start with a short lifetime and lengthen it once you know how often the data really changes.
⚠️
A cached function cannot read request data: cookies, headers and search parameters are not part of its cache key. If a cached render reads them, one visitor's output can be served to another. Keep request values in the dynamic shell around the cached part.

Invalidating on demand

'use server'

import { revalidatePath, revalidateTag } from 'next/cache'
import { db } from '@/lib/db'

export async function publishGuide(id) {
  await db.guide.update({ where: { id }, data: { published: true } })

  revalidateTag('guides')             // every entry tagged 'guides'
  revalidatePath('/guides')           // the route and everything cached below it
  // revalidatePath('/guides', 'page')  // only that page, not nested segments
}
  • Revalidate on the write, in the same action or handler. A cache entry cleared by a scheduled job is stale until that job happens to run.
  • revalidatePath takes a route path, not a URL with a query string, and clears the full route cache for it.
  • revalidateTag scales better: the code doing the write does not need to know which routes render the data.
  • After invalidation the next request renders fresh output and repopulates the cache, so the first visitor after a publish pays the render cost.
  • Development bypasses much of the caching, so verify cache behaviour against a production build before drawing conclusions.

FAQ

Why is stale data still showing after a write?
Check three places: the data cache (did you tag and revalidate it), the full route cache (is the page static), and the client-side router cache plus any CDN in front. A hard refresh clears the last two, which is how you tell them apart.
Do I have to cache anything?
No. Everything can be dynamic, and for a small app that is simpler. Caching is worth the complexity when a page is expensive to render and its data changes on a schedule you can describe.

Dynamic routes, params and metadata Server actions and forms in depth

Last refreshed 2026-09-18.