Next.js 16 `use cache` Directive: Granular Caching at the Function Level and What It Replaces Next.js 16 replaces its implicit caching model with an explicit `use cache` directive applied at the function and component level, paired with `cacheLife()` for expiration and `cacheTag()` for on-demand invalidation. The release removes `unstable_cache`, fetch cache options like `next.revalidate`, and compiler-inferred route segment caching, unifying database queries, fetch calls, and React Server Components under one caching syntax. A `use cache: remote` variant moves entries to a shared durable handler for higher hit rates. use cache Directive: Granular Caching at the Function Level and What It Replaces This article was written with the assistance of AI, under human supervision and review. Next.js 16 eliminates the framework's most confusing aspect: implicit caching. Previous versions cached fetch requests automatically, left developers guessing when route segments became static, and buried cache configuration inside fetch options. The new use cache directive makes every cache decision explicit. Developers mark what to cache, set expiration with cacheLife , and invalidate with cacheTag . The old model failed because developers could not predict cache behavior. A fetch call with no options might cache forever or not cache at all, depending on whether the route contained dynamic functions. Route segments became static or dynamic based on heuristics the compiler inferred from your code. Production bugs emerged when a component that worked dynamically in development suddenly rendered statically in production, serving stale data for hours. Problem: implicit caching causes unpredictable cache behavior and stale data Next.js 16 solves this with use cache . When you add the directive to a function or component, the framework caches its return value. When you call cacheLife 'hours' , the cache expires in one hour. When you call cacheTag 'products' , you can invalidate that cache entry on demand. The mental model becomes: mark what you cache, state when it expires, name it for invalidation. Solution: explicit use cache directive gives predictable control This post covers what use cache replaces, how function-level and component-level caching work, migration patterns from unstable cache , the cache hierarchy Next.js now uses, and production gotchas around cache storage. unstable cache , fetch cache options, and all implicit caching; every cache decision is now explicit. cacheLife sets expiration windows; cacheTag enables on-demand invalidation; both pair with use cache: remote moves entries to a shared durable handler for high hit rates. next.revalidate with use cache supersedes three caching mechanisms from Next.js 15 and earlier. unstable cache : developers wrapped functions in unstable cache fn, keyParts, options to cache database queries or API calls. The wrapper returned a new function that memoized results. The name signaled instability; the API never graduated to stable. Next.js 16 removes it entirely. fetch cache options : developers passed { next: { revalidate: 3600 } } to fetch calls to cache HTTP responses. The framework intercepted fetch, stored the response in a data cache, and served cached data until expiration. This worked only for fetch calls. Database queries and third-party SDK calls required unstable cache . Implicit route caching : Next.js analyzed route segments at build time. If a segment contained no dynamic functions cookies, headers, searchParams , the compiler marked it static and prerendered it. If any child component called a dynamic function, the entire segment became dynamic. Developers could not see this decision; they inferred it from build output or production behavior. All three mechanisms are gone. use cache handles every scenario. Diagram showing old caching mechanisms replaced by use cache The removal creates a single model. You mark cacheable operations with use cache , set expiration with cacheLife , and invalidate with cacheTag . The compiler generates cache keys from function arguments and captured closures. No wrapper functions, no fetch-specific options, no implicit heuristics. This unification matters because developers now reason about caching the same way everywhere. A database query, a fetch call, and a React Server Component all use identical syntax. The cache key derivation follows the same rules. The expiration model applies uniformly. The use cache directive applies to async functions and React Server Components. When Next.js encounters it, the framework serializes the function's return value into an RSC payload and stores that payload in a cache. Function-level caching targets data operations. Add use cache to the top of an async function that fetches from a database, calls a third-party API, or performs expensive computation: js import { cacheLife } from 'next/cache'; export async function getProduct id: string { 'use cache'; cacheLife 'hours' ; const product = await db.query 'SELECT FROM products WHERE id = $1', id ; return product; } The compiler generates a cache key from the function identity and its arguments. In this example, the key includes getProduct and the id value. Calling getProduct '123' creates one cache entry; calling getProduct '456' creates another. The framework does not cache the function itself; it caches the result for each unique set of arguments. Component-level caching targets UI trees. Add use cache to an async component that renders JSX: js import { cacheLife } from 'next/cache'; export default async function ProductList { 'use cache'; cacheLife 'minutes' ; const products = await db.query 'SELECT FROM products' ; return