Next.js path · Rendering and caching · Topic 4 of 11

use cache and cacheLife

Not started

With Cache Components enabled, caching in Next.js is explicit and lives in your code. Put "use cache" at the top of an async function (data-level) or a component or page (UI-level), and Next.js stores its return value under a key made from its inputs. A cached result can be included in the static shell at build time, reused across requests at runtime, and kept in the browser's router cache for navigations. Everything you don't mark is fetched fresh for each request, which is the safe default.

Why it matters: Since Next.js 15 nothing is cached unless you ask, and in 16 the way you ask is one directive. Knowing what goes into the key and how long an entry lives is the difference between a fast page and one that shows another user's data or never updates.

  1. 1Opt in, per function

    `"use cache"` at the top of an async function, component or file caches its return value. Without it, `fetch` and database calls run every time.

  2. 2The key is built for you

    The build ID, the function's identity, its serialisable arguments and any variables it closes over form the key. Different inputs give different entries.

  3. 3Three times in a lifetime

    `cacheLife` sets `stale` (how long the browser reuses it), `revalidate` (after which the server refreshes it in the background) and `expire` (after which the next request waits for fresh data).

  4. 4Where entries live

    By default in server memory, which serverless instances may not keep. `use cache: remote` uses a shared cache handler; `use cache: private` is for per-user results and is never stored on the server across requests.

1

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.
⌘/Ctrl + Enter
OutputNot run yet

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.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.Inside a function marked `"use cache"` you call `cookies()` to find the user. What happens?
  2. 2.A function has `cacheLife({ revalidate: 3600, expire: 86400 })`. A request arrives 2 hours after the entry was made. What does it get?
  3. 3.Your app runs on serverless functions and a `use cache` database query still hits the database on most requests. Why?

0 of 3 right so far. Get all three to finish this topic.