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
fetchby 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
generateMetadatais 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.cacheLifedescribes how long a value stays fresh, when it refreshes in the background and when it expires — one profile instead of three separate numbers.cacheTagattaches 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.
revalidatePathtakes a route path, not a URL with a query string, and clears the full route cache for it.revalidateTagscales 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.
Related
Dynamic routes, params and metadata Server actions and forms in depth
Last refreshed 2026-09-18.