Skip to content
  • Next.js
  • Caching
  • React
  • Performance
  • Web Development

Next.js 16 Caching Explained: use cache, cacheLife and updateTag vs revalidateTag

By Published 4 min read (or skim, I won't know)

In Next.js 16, caching is explicit: nothing is cached until you mark it with the 'use cache' directive. You set how long it lives with cacheLife, label it with cacheTag, and invalidate it with updateTag (immediately) or revalidateTag (in the background).

What changed in Next.js 16 caching?

Earlier versions of the App Router cached a lot implicitly, which made it hard to know why a page showed stale data. Next.js 16 flips the default with Cache Components:

Before (Next.js 14–15)Next.js 16 with Cache Components
DefaultMany fetches and routes cached implicitlyNothing cached unless you say so
How to cachefetch options, export const revalidate'use cache' + cacheLife
InvalidaterevalidateTag(tag), revalidatePathupdateTag, revalidateTag(tag, profile), revalidatePath
Static + dynamic in one pageHardPartial Prerendering by default

Turn it on in next.config.ts (new apps already have it):

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  cacheComponents: true,
};

export default nextConfig;

How do you use the 'use cache' directive?

Put 'use cache' at the top of a function, a component or a whole file. The function's arguments become part of the cache key, so getProducts("shoes") and getProducts("bags") are cached separately.

import { cacheLife, cacheTag } from "next/cache";

export async function getProducts(category: string) {
  "use cache";
  cacheLife("hours");          // how long it stays fresh
  cacheTag("products");        // a label to invalidate it later
  return db.product.findMany({ where: { category } });
}

A cached component works the same way:

export async function Footer() {
  "use cache";
  const links = await getFooterLinks();
  return <footer>{/* … */}</footer>;
}
PlacementWhat gets cached
Top of a fileEvery export (they must all be async)
Inside a componentIts rendered output
Inside a functionIts return value

What do the cacheLife profiles mean?

Each profile sets three times. stale is how long the browser may reuse data without asking; revalidate is when the server starts refreshing in the background; expire is when old data can no longer be served at all.

Profilestalerevalidateexpire
"seconds"30 s1 s1 min
"minutes"5 min1 min1 h
"hours"5 min1 h1 day
"days"5 min1 day1 week
"weeks"5 min1 week30 days
"max"5 min30 days1 year

You can also pass numbers in seconds, for example cacheLife({ revalidate: 600 }), or define named profiles under cacheLife in next.config.ts.

updateTag vs revalidateTag: which one should you use?

Both invalidate data you labelled with cacheTag. The difference is when users see fresh data.

updateTag("posts")revalidateTag("posts", "max")
EffectExpires immediately; next read waits for fresh dataMarks stale; old data served while fresh data loads
Called fromServer Actions onlyServer Actions and route handlers
Use forRead-your-own-writes: forms, settings, "I just posted this"CMS webhooks, background jobs, data others can see a moment later
"use server";
import { updateTag } from "next/cache";

export async function createPost(formData: FormData) {
  await db.post.create({ data: { title: String(formData.get("title")) } });
  updateTag("posts");          // the author sees the new post right away
}

Two related helpers: revalidatePath("/blog") invalidates everything for a route, and refresh() (Server Actions only) re-renders the client router without touching cached data.

How does Partial Prerendering fit in?

With Cache Components, one page can be both static and dynamic. Cached parts are prerendered into a static shell that is served instantly, and parts that read request data (cookies(), headers(), searchParams) stream in at request time. Those dynamic parts must sit inside a <Suspense> boundary:

import { Suspense } from "react";
import { cookies } from "next/headers";

async function Cart() {
  const id = (await cookies()).get("cartId")?.value;   // request-time data
  return <CartList items={await getCart(id)} />;
}

export default function ProductPage() {
  return (
    <>
      <ProductInfo />                                  {/* 'use cache' → static shell */}
      <Suspense fallback={<CartSkeleton />}>
        <Cart />                                       {/* streams in */}
      </Suspense>
    </>
  );
}

What are the common caching mistakes in Next.js 16?

  • Reading cookies inside 'use cache'. A shared cache cannot depend on one user. Read the cookie outside and pass the value as an argument, or use 'use cache: private'.
  • Keeping old segment config. export const revalidate, dynamic, fetchCache and dynamicParams error when Cache Components is on.
  • Using revalidateTag where updateTag is needed. If the user who made the change sees old data after submitting a form, switch to updateTag.
  • Request-time data outside Suspense. Wrap components that read cookies, headers or search params in <Suspense> so the rest of the page can still be prerendered.

Key takeaway

Cache on purpose. Add 'use cache' where data is shared and changes rarely, pick a cacheLife profile that matches how fresh it must be, tag it, and invalidate with updateTag for the person who just changed it or revalidateTag(tag, "max") for everyone else. For a one-page summary of the rest of Next.js 16, see the free Next.js handwritten notes.

Frequently asked questions

Is fetch cached by default in Next.js 16?

No. In Next.js 16 with Cache Components, fetch requests and database calls are not cached by default. You opt in by adding the 'use cache' directive to a function, a component or a whole file, and you control the lifetime with cacheLife.

What is the difference between updateTag and revalidateTag in Next.js 16?

updateTag expires a cache tag immediately, so the next request waits for fresh data; it can only be called from Server Actions and is meant for read-your-own-writes, such as showing a user the post they just created. revalidateTag(tag, 'max') marks the tag stale and serves the old data while fresh data loads in the background, which suits webhooks and background updates.

How do I enable Cache Components in Next.js 16?

Set cacheComponents: true in next.config.ts. New projects created with create-next-app in Next.js 16 have it enabled already.

Can I use cookies() or headers() inside 'use cache'?

No. A shared cache cannot depend on one user's request. Read cookies() or headers() outside the cached function and pass the value in as an argument, or use the 'use cache: private' variant, which caches per user in the browser.

Do export const revalidate and export const dynamic still work in Next.js 16?

Not when Cache Components is enabled. The route segment options dynamic, revalidate, fetchCache and dynamicParams cause an error with cacheComponents on; use 'use cache' with cacheLife and Suspense boundaries instead. They still work in apps that have not enabled Cache Components.