afaqrashid.com  ·  portfolio · 2026 ed.

Next.js "use cache" Not Working? cacheComponents Errors and Fixes, Tested on Next.js 16

"use cache" marks a function, component or page whose result Next.js should cache. It's part of Cache Components, the caching model in Next.js 16, and turning it on changes the rules for every page, not only the ones that use the directive. That's why most "use cache not working" problems are really build errors in other pages.

I turned it on in a Next.js 16.4.4 project and built each common mistake on its own, then measured how long cached values lasted.

Step 0: it needs a config flag

Error: To use "use cache", please enable the feature flag `cacheComponents` in your Next.js config.
// next.config.ts
import type { NextConfig } from "next";

const config: NextConfig = { cacheComponents: true };
export default config;

Error 1: uncached or runtime data during prerendering

With cacheComponents on, a page that read cookies() or did a plain fetch() at the top level failed the build. The wording in 16.4.4 is different from the "Uncached data was accessed outside of <Suspense>" that older answers quote:

Error: Route "/account": Next.js encountered uncached or runtime data during prerendering.
`fetch(...)`, `cookies()`, `headers()`, `params`, `searchParams`, or `connection()` accessed outside of `<Suspense>`
prevents the route from being prerendered, blocking the page load and leading to a slower user experience.
Ways to fix this:
  - [stream] Provide a placeholder with `<Suspense fallback={...}>` around the data access
  - [cache] For uncached data (`fetch`, database calls): cache the access with "use cache"
  - [block] Set `export const instant = false` to allow a blocking route

Next now wants every page to have a static shell it can send at once. Anything that depends on the request, or isn't cached, has to sit inside a Suspense boundary so the shell can go out without it. The message lists three fixes, and I built all three:

// [stream] for request data: cookies, headers, searchParams
import { Suspense } from "react";
import { cookies } from "next/headers";

async function Theme() {
  const theme = (await cookies()).get("theme")?.value ?? "light";
  return <span>{theme}</span>;
}

export default function Page() {
  return (
    <main>
      <h1>Account</h1>
      <Suspense fallback={<span>loading</span>}>
        <Theme />
      </Suspense>
    </main>
  );
}

// [cache] for data that's the same for every visitor
async function getProducts() {
  "use cache";
  const res = await fetch("https://api.example.com/products");
  return res.json();
}

The Suspense version built as Partial Prerender (◐ in the build output): the heading was in the static HTML and the cookie value streamed in, showing "dark" for a request with theme=dark. The cached fetch built as a fully static page. export const instant = false also built, as a dynamic route (ƒ) that waits for the data before sending anything. That's the old behaviour, so use it only where streaming doesn't make sense.

Error 2: new Date() while prerendering

Error: Route "/k-date": Next.js encountered the unstable value `new Date()` while prerendering.
This value can change between renders, so it must be either prerendered or computed later.

A component that only printed new Date() failed. The fixes it lists: put await connection() before it to render at request time, cache it with "use cache" if one value for everyone is fine, or render it in a client component.

Error 3: cookies() inside "use cache"

Error: Route "/k-incache": `cookies()` can't be read inside `"use cache"`.
Read it outside the cached function and pass what you need as an argument.

A cached result is shared between visitors, so it can't read one visitor's cookies. Read the cookie in the component and pass the value in. The arguments become part of the cache key: in my test two requests with theme=dark got the same cached result, and theme=light got a separate one.

async function getLabel(theme: string) {
  "use cache";
  return expensiveLookup(theme); // cached once per theme value
}

async function Label() {
  const theme = (await cookies()).get("theme")?.value ?? "light";
  return <span>{await getLabel(theme)}</span>;
}

Error 4: export const revalidate and dynamic stop working

Error: Route segment config "revalidate" is not compatible with `nextConfig.cacheComponents`. Please remove it.
Error: Route segment config "dynamic" is not compatible with `nextConfig.cacheComponents`. Please remove it.

Both failed the build immediately. Under Cache Components, cache lifetime is set per cached function with cacheLife(), and "dynamic" is whatever isn't cached, so these page-level settings are gone. When you migrate, search the codebase for export const revalidate and export const dynamic first: every one of them breaks the build.

How long a "use cache" value lasts

With no cacheLife(), the build output listed the cached pages with Revalidate 15m and Expire 1y. At runtime, a cached timestamp stayed the same on every request in my test. A function with cacheLife("seconds") returned a new value on each request made two seconds apart.

import { cacheLife } from "next/cache";

async function getPrices() {
  "use cache";
  cacheLife("hours"); // or "seconds", "minutes", "days", "weeks", "max", or an object
  return fetchPrices();
}

Clearing it: updateTag vs revalidateTag

import { cacheTag, updateTag, revalidateTag } from "next/cache";

async function getPosts() {
  "use cache";
  cacheTag("posts");
  return db.post.findMany();
}

// in a Server Action, after saving:
updateTag("posts");          // next render reads fresh data
// or
revalidateTag("posts", "max"); // stale-while-revalidate

The difference was visible in the browser. After a Server Action called updateTag, the page re-rendered with the new value straight away. After revalidateTag with "max", the page still showed the old value, the first reload still showed it, and the second reload had the new one. Use updateTag when the user must see their own change (they just saved a form), and revalidateTag when slightly stale content is fine.

updateTag only works in Server Actions. Called from a Route Handler (for example a CMS webhook) it threw: "updateTag can only be called from within a Server Action. To invalidate cache tags in Route Handlers or other contexts, use revalidateTag instead."

All results come from one Next.js 16.4.4 project with cacheComponents: true, built with next build and run with next start on Node 26. Browser checks were in Chrome.

Related

Sources