How it works
The explanation, with examples you can run.
Two levels
import { cacheLife, cacheTag } from "next/cache" // Data-level: any component can call it; one entry per idexport async function getProduct(id: string) { "use cache" cacheLife("hours") cacheTag(`product-${id}`) return db.product.findUnique({ where: { id } })} // UI-level: the rendered output is cachedexport async function Footer() { "use cache" cacheLife("max") const links = await cms.footerLinks() return <nav>{links.map((l) => <a key={l.href} href={l.href}>{l.label}</a>)}</nav>}The key includes the build ID (so a deploy starts afresh), the function's identity, its arguments and any variables it captures from the enclosing scope. Arguments must be serialisable; children and Server Actions can be passed through as long as the cached function doesn't look inside them. Cached scopes cannot call cookies(), headers() or read searchParams, even in a helper deeper in the call stack; read them outside and pass the value in, which also makes it part of the key.
How long an entry lives
cacheLife takes a profile (seconds, minutes, hours, days, weeks, max; default if you call nothing) or an object with three times:
- stale: how long the client router may reuse the result without asking the server.
- revalidate: after this, the next request gets the cached value and triggers a background refresh.
- expire: after this, the next request waits for a fresh value.
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
Very short lifetimes (the seconds profile, revalidate: 0, or expire under five minutes) are left out of the static shell and become streamed holes, since prerendering them would be pointless.
Where entries are stored
The default handler keeps runtime entries in an in-memory LRU on each server instance. On a long-running Node server they are reused; on serverless platforms an instance may serve one request and vanish, so the function often runs again. "use cache: remote" asks the platform's configured cache handler (for example Redis or a KV store) to keep entries for all instances. "use cache: private" is the opposite: it may read cookies and headers, results are reused only within one request and in that user's browser, never in a shared server cache.
Apps that have not enabled Cache Components use the previous model, where fetch(url, { cache: "force-cache" }), unstable_cache and route segment options such as revalidate do this job. Since Next.js 15, plain fetch is not cached by default in either model.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.