Why `cookies()` and `headers()` Cannot Be Used Inside `use cache`

A cached scope must return the same result for everyone, so reading the request inside use cache fails. Learn the error and how to pass values in instead.

6 min read

A cached scope cannot read the incoming request. cookies() and headers() are request-time APIs, and Next.js blocks them inside use cache because a cached result must be identical for every visitor.

Reading request data would make the entry per-request, which defeats the point. The point of caching is to compute once and reuse for many visitors, so anything that differs per visitor has no place in the cached body.

The failure surfaces as the next-request-in-use-cache error, which Next.js documents under this title:

texttext
Cannot access `cookies()` or `headers()` in `"use cache"`

The restriction follows the call stack, so even a helper the cached function calls fails the same way. A cached entry is keyed by its inputs, and a request value changes on every call, so the entry could never be reused.

The broken version

The mistake is easy to make, because the directive and the cookie read sit in the same function. This function reads a cookie inside the cached scope and cannot prerender:

typescripttypescript
// app/lib/preferences.ts
import { cookies } from 'next/headers'
 
export async function getPreferences() {
  'use cache'
  const theme = (await cookies()).get('theme')?.value || 'light'
  const res = await fetch(`https://api.example.com/preferences?theme=${theme}`)
  return res.json()
}

On a dynamically rendered route this may pass the build and then fail under next start, because the request data is not available while prerendering. The subtle part is that the build itself can look green while the production server is the one that errors.

The fix: pass values in

Read the cookie outside the cached scope and pass the extracted value as an argument.

typescripttypescript
// app/lib/preferences.ts
import { cookies } from 'next/headers'
 
export async function getPreferences(theme: string) {
  'use cache'
  const res = await fetch(`https://api.example.com/preferences?theme=${theme}`)
  return res.json()
}
 
export async function readPreferences() {
  const theme = (await cookies()).get('theme')?.value || 'light'
  return getPreferences(theme)
}

The theme value now joins the cache key, so each theme gets its own shared entry. The caller reads the request, the cached function only sees a plain string, and it can prerender again. The same approach works for any request value: extract it outside, then hand the cached function a plain argument.

What about searchParams

The same rule applies to searchParams. Read the value in the page, then pass it into the cached function rather than reading the promise inside the cached scope.

Route params follow the same boundary: await the promise in the page and hand the cached function the plain value. Draft mode is the one partial exception, because you may read its isEnabled flag inside a cached scope. Static values, arguments, and captured module-level data are all fine, only request-time data is blocked.

When you truly need request data inside

For the rare case where you cannot refactor, the use cache: private variant allows reading cookies, headers, and searchParams inside the scope, but it stores results only in the browser and never on the server. That is the tradeoff: you keep the request access but lose shared caching.

See use cache: private explained.

For where the directive can be placed, see the use cache directive.

Rune AI

Rune AI

Key Insights

  • cookies(), headers(), and searchParams are blocked inside use cache.
  • Reading them fails with the next-request-in-use-cache error.
  • The fix is to read outside and pass values as arguments.
  • Passed arguments become part of the cache key.
  • use cache: private is the exception for browser-only results.
RunePowered by Rune AI

Frequently Asked Questions

Is searchParams also blocked inside use cache?

Yes. searchParams is request data too, so it is blocked the same way. Read it outside the cached scope and pass the value in.

What is the fix when I genuinely need per-user data?

Read the value outside the cached function and pass it as an argument, or use the use cache: private variant for browser-only caching.

Conclusion

Cached scopes must be deterministic, so request APIs like cookies, headers, and searchParams are blocked inside use cache. Read them outside the scope and pass the extracted values as arguments, which also makes them part of the cache key.