DEV Community

Cover image for Next.js 16 "use cache" Errors: Why Cache Components Break
Mahdi BEN RHOUMA
Mahdi BEN RHOUMA

Posted on Originally published at iloveblogs.blog

Next.js 16 "use cache" Errors: Why Cache Components Break

Next.js 16 flipped the caching model. Through Next.js 15, pages and fetches were cached by default and you opted out. With cacheComponents: true in Next.js 16, nothing is cached by default and you opt in with the 'use cache' directive. That's a bigger behavioral change than the version bump suggests, and it produces a specific, recurring family of build and runtime errors that generic "here's what use cache does" tutorials don't cover, because they're written against a fresh demo app, not a real one with i18n, auth, and third-party SDKs already wired in.

This is what actually breaks, and how to fix each case, based on the current cacheComponents docs and reports like amannn/next-intl#2229.

First: confirm you're actually opted in

'use cache' only does anything once cacheComponents: true is set in next.config.ts. If you added the directive but nothing changed, that's the first thing to check — it's a silent no-op without the flag, which itself causes confusion ("I added use cache and nothing happened").

// next.config.ts
const nextConfig = {
  cacheComponents: true,
}
Enter fullscreen mode Exit fullscreen mode

Error 1 — "headers() / cookies() cannot be called inside 'use cache'"

This is the single most common failure once you enable cacheComponents. It happens whenever a function marked 'use cache' — directly, or indirectly through something it calls — reads headers(), cookies(), or any other request-scoped API. The confirmed next-intl case (#2229) is a textbook example: getTranslations() reads locale information from headers() internally, so wrapping a component that calls it in 'use cache' throws even though you passed a locale prop explicitly — the library ignores your prop and reaches into the request anyway.

Fix: cached functions must be pure with respect to the request. Move the 'use cache' boundary below anything that reads request state — cache the pure data-fetching function, not the component that also renders user-specific chrome. If a third-party library reads request context internally (i18n, auth SDKs, geolocation), don't wrap components that use it in 'use cache' at all; cache only the deterministic parts around it.

Error 2 — cached value never updates (missing cacheLife)

If your cached function runs once and then never refreshes, you're missing cacheLife. Without it, Next.js applies a default profile that may be far more aggressive than you expect for a data-heavy page.

import { unstable_cacheLife as cacheLife } from 'next/cache'

async function getPosts() {
  'use cache'
  cacheLife('minutes') // stale/revalidate/expire window — pick the built-in profile that matches your data
  return db.posts.findMany()
}
Enter fullscreen mode Exit fullscreen mode

cacheLife takes three practical knobs: how long the cache serves stale before checking, when it triggers a background refresh, and the hard expiry before eviction. Skipping it doesn't disable caching — it just applies defaults you didn't choose.

Error 3 — mutation doesn't invalidate the cache

If a user updates data and still sees the old version, you're missing cacheTag plus a matching revalidateTag/updateTag call in the mutation.

async function getProject(id: string) {
  'use cache'
  cacheTag(`project-${id}`)
  return db.projects.findUnique({ where: { id } })
}

// in the Server Action that mutates it:
import { revalidateTag } from 'next/cache'
revalidateTag(`project-${id}`)
Enter fullscreen mode Exit fullscreen mode

Without a tag, there's nothing to invalidate — the cache entry sits until its cacheLife expiry regardless of what your mutation does.

Error 4 — putting 'use cache' on the page itself

Best practice from the current Next.js docs: don't put 'use cache' directly on a page component. Pages are orchestration layers that assemble Server Components, params, and search params — none of which are cache-safe by nature. Put it on the data-fetching function or on a cached child Server Component instead, and let the page stay dynamic around it. Caching the page directly is what produces the confusing "why is my whole page stale/stuck" reports.

If you're migrating from unstable_cache

unstable_cache still works, but it's the old, less granular API. The practical migration path is: replace unstable_cache(fn, keyParts, options) calls with a 'use cache' function plus explicit cacheLife/cacheTag calls — the tag/key logic maps roughly 1:1, but cacheLife profiles replace the old revalidate option with more granular stale/revalidate/expire control.

Related reading

If you're also hitting a /_not-found prerender crash while doing this migration, that's very likely a separate Next.js 16 issue, not this one — see Next.js 16 /_not-found prerender build error: the real fix. For the Next.js 15 caching model this replaces, see Next.js 15 caching, explained and Next.js stale cache / revalidation fix. For the Supabase side of a cached data layer, see Next.js + Supabase caching strategies.


Originally published at https://www.iloveblogs.blog

Top comments (0)