Next.js error
You're Importing a Component That Needs next/headers
The error
You're importing a component that needs next/headers. That only works in a Server Component but one of its parents is marked with "use client", so it's a Client Component.A server-only API was pulled into a client tree, usually by a shared helper. The error names the file; the cause is the boundary above it.
cookies(), headers() and draftMode() from next/headers read the
incoming request. There is no incoming request in the browser, so they exist
only in Server Components, route handlers and Server Actions.
The error appears when one of them ends up beneath a 'use client' boundary.
Note what it actually says: one of its parents is marked. The file that
throws is often not the file that changed.
Move the read up, not the directive down
The instinct is to make the component that needs the header a Server Component. That does not work if its parent is a Client Component - a Client Component cannot render a Server Component as a child.
What it can do is receive one as a prop or as children, rendered by a server parent:
// app/page.tsx - Server Component
import { cookies } from 'next/headers';
import { Shell } from './shell'; // 'use client'
export default async function Page() {
const theme = (await cookies()).get('theme')?.value ?? 'light';
return <Shell theme={theme}>{/* ... */}</Shell>;
}The cookie is read where it can be read. Shell receives a string. Nothing
crosses the boundary that cannot serialise.
This is the general shape: request data enters at the top of the tree and is passed down. Reaching for it in the middle is what produces this error.
The shared-helper case
The version that takes longer to find:
app/dashboard/layout.tsx 'use client'
└─ <UserMenu />
└─ lib/session.ts → calls cookies()
lib/session.ts is imported by six server files and one client one. It was
fine until layout.tsx gained a directive - maybe for an animation - and took
its whole subtree with it.
Two useful moves here. Split the helper so the part that reads the request is
separate from the part that is pure. And add import 'server-only' to the
request-reading half, which turns this into a build error at the exact import
rather than a runtime surprise in one route.
What looks like a fix and is not
Removing 'use client' from the parent. Sometimes correct, and worth
checking - directives accumulate and some are no longer needed. But if the
parent genuinely has state or handlers, removing it trades this error for
a different one.
Reading the cookie in an effect and fetching it from an API route. This works and costs a round trip after hydration, plus a flash of the wrong state. For a theme, that flash is the thing users notice.
Passing the whole cookies() object down. It does not serialise, and you
get
a different error again. Pass
the value, not the accessor.
The consequence nobody mentions
Reading request data is also what takes a route out of static rendering, so fixing this error by moving the read upward has a second effect worth being deliberate about: the route becomes dynamic, and everything under it too. Contain it with a Suspense boundary and the rest of the page stays static.
