'use cache'

The 'use cache' Directive and Cache Keys

"use cache" goes at the top of an async function body, of a component, or of a whole file — in which case every export it covers must be async. Compare two pages doing the same work. The first is the default: a render that has to happen per request, so it needs connection() and a boundary.

Dynamic by default (app/live/page.js)JavaScript
async function Clock() {
  await connection();
  return <p>rendered at {new Date().toISOString()} — render #{bump("live")}</p>;
}

Its page renders <Suspense fallback={<p>Loading...</p>}><Clock /></Suspense>. The cached version swaps connection() for a directive and needs no boundary. bump() is a counter in a plain module returning how many times it has been called, so the page reports how often the body actually ran.

The same component, cached (app/cached/page.js)JavaScript
async function Clock() {
  "use cache";
  cacheLife("max");
  return <p>rendered at {new Date().toISOString()} — render #{bump("cached")}</p>;
}

Three requests to each, against next start after a build that finished at 05:57:55:

Output of 35
/live    rendered at 2026-09-22T05:58:31.642Z — render #1
         rendered at 2026-09-22T05:58:31.827Z — render #2
         rendered at 2026-09-22T05:58:31.977Z — render #3
/cached  rendered at 2026-09-22T05:57:55.278Z — render #1
         rendered at 2026-09-22T05:57:55.278Z — render #1
         rendered at 2026-09-22T05:57:55.278Z — render #1

The cached body ran once, at build time, and every request since replayed that result. new Date() is allowed inside use cache — the value freezes with the entry — but outside one it is a build error.

What makes an entry unique

A cache key hashes four things: the build ID, the function's location and signature, the serialized arguments, and, in development only, a hot-reload hash. Arguments include anything captured from an enclosing scope:

Both values are part of the keyJavaScript
async function Component({ userId }) {
  const getData = async (filter) => {
    "use cache";
    return (await fetch(`https://api.example.com/u/${userId}?f=${filter}`)).json();
  };
  return getData("active");
}

userId comes from the closure and filter from the call, and each combination gets its own entry. Because the build ID is in the key, no entry survives a deployment.

Arguments must satisfy React 7,897 's Server Component serialization — primitives, plain objects, arrays, Date, Map, Set, typed arrays — and return values the looser Client Component rules, which also allow JSX. Class instances, functions and URL objects are rejected. The exception is pass-through: children, other slots and Server Actions may be re-emitted untouched, which is what lets a cached layout wrap dynamic children.