afaqrashid.com  ·  portfolio · 2026 ed.

"cookies was called outside a request scope" in Next.js: 6 Causes Tested

cookies(), headers() and draftMode() read the current HTTP request. Next keeps that request in a context that only exists while it's handling one. Call them anywhere else (at build time, when a file is first loaded, in a script or a test) and there's no request to read, so you get:

`cookies` was called outside a request scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context

headers and draftMode give the same message with their own name. I reproduced every case below on Next.js 16.4.4 (Node 26) with next build and next start. Some of them now give a more specific error, which is worth knowing when you search your logs.

What I tested

where cookies() was called           what happened
plain Node script or test file       "cookies was called outside a request scope"
instrumentation.ts register()        "cookies was called outside a request scope"
top level of a page file             build failed: "Failed to collect configuration", same message
generateStaticParams                 build failed: "used cookies() inside generateStaticParams"
inside unstable_cache()              build failed: "cookies() can't be read inside unstable_cache()"
inside after() in a page             "used cookies() inside after() while rendering. This is not supported"
inside after() in a Route Handler    worked
inside setTimeout during a request   worked
a helper in lib/ called from a page  worked

1. Scripts, tests and seed files

Importing a function that calls cookies() from a plain Node script (a seed script, a cron job, a Jest or Vitest test) fails straight away, because nothing set up a request. The fix is to keep request reading at the edge: the page, action or route reads the cookie and passes the value into the function you want to reuse.

// lib/orders.ts: no next/headers here, so scripts and tests can call it
export async function getOrders(userId: string) { /* ... */ }

// app/orders/page.tsx
import { cookies } from "next/headers";
import { getOrders } from "@/lib/orders";

export default async function Page() {
  const userId = (await cookies()).get("uid")?.value;
  const orders = userId ? await getOrders(userId) : [];
  // ...
}

2. instrumentation.ts

register() runs once when the server starts, before any request, so cookies() there failed with the same message. If you need per-request data for logging or tracing, read it in proxy or in the route itself.

3. The top level of a file

import { cookies } from "next/headers";
const early = cookies(); // runs when the file is loaded, not per request

export default async function Page() { /* ... */ }

This failed the build with "Failed to collect configuration for /o-module" and the request scope message underneath. It throws immediately, so a .catch() on the promise didn't stop it. Move the call inside the component or function, so it runs once per request.

4. generateStaticParams

Error: Route /o-gsp/[id] used `cookies()` inside `generateStaticParams`. This is not supported because `generateStaticParams` runs at build time without an HTTP request.

There's no visitor at build time, so there are no cookies. Build the list of params from your data source only. If the page needs to vary per visitor, read cookies in the page itself, which makes it dynamic.

5. unstable_cache

Error: Route "/o-cache": `cookies()` can't be read inside `unstable_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 depend on one visitor's cookies. Do what the message says: read the cookie outside, and pass the value in. It then becomes part of the cache key.

const getPrices = unstable_cache(async (currency: string) => fetchPrices(currency), ["prices"]);

export default async function Page() {
  const currency = (await cookies()).get("currency")?.value ?? "USD";
  const prices = await getPrices(currency);
  // ...
}

6. after() in a page or layout

Route /o-after used `cookies()` inside `after()` while rendering. This is not supported. If you need this data inside an `after()` callback, use `cookies()` outside of the callback.

after() in a page runs after the response is sent, and Next doesn't allow request APIs in it there. The same code inside a Route Handler's after() worked and logged the cookie value. In a page, read the value first and use it in the callback. That version logged "dark" for a request with theme=dark.

import { cookies } from "next/headers";
import { after } from "next/server";

export default async function Page() {
  const theme = (await cookies()).get("theme")?.value ?? "none";
  after(() => logView(theme));
  return <main>...</main>;
}

What doesn't cause it

Moving cookies() into a helper file is fine: a lib/ function called from a page read theme=dark correctly. So did a setTimeout started during a request, because the request context carries over to it. If your error appeared after a refactor, look for a call that now runs outside a request (at load time, at build time or in a script), not for the file it moved to.

One more trap: a page that only calls cookies() inside a callback that runs later (setTimeout, after()) still built as a static page. Next can't see that call when it decides. In my test the after() callback ran once, during the build, and the setTimeout one never ran at all; neither ran per visitor. Read cookies in the component body, or call connection() first, if the page must render per request.

Related

Sources