Errors and fixes
A Server Component importing a client hook: the error and the three ways out
A Server Component cannot use state, effects or browser APIs. The error is explicit, and the fix is to move the boundary, not to bypass it with a cast.
Key points
- `"use client"` goes in the file that uses the hook, not in any ancestor: the boundary is per module.
- A Server Component can pass serialisable props to a Client Component, never functions.
- Move the hook to the smallest leaf: the client boundary stays small and the bundle does not grow.
Short answer: a Server Component cannot use hooks. Add "use client" to the file that holds the hook, or move the hook into the smallest leaf you can find. Do not bypass the error with a cast.
The exact message
You're importing a component that needs `useState`. This React hook
only works in a Client Component but none of its parents are marked
with a "use client" directive, so they're Server Components by default.
The message may name useEffect or useRef: the hook changes, the cause does not. React reports the first hook it meets in the module.
How the boundary works
React compiles modules one at a time and the directive marks a module, not a component. From there the bundler builds two graphs, one for the server and one for the client. The server graph never exposes hooks, because the build resolves react with the react-server condition, which carries only the server half. A file without the directive is compiled into that graph, so useState is not an export and the build stops before running any code.
In the App Router the default is server, and it applies to children too: everything a client module imports becomes client, while props coming from a server parent stay rendered on the server. The practical consequence is that the boundary is drawn from the bottom up: one "use client" at the top of the tree pulls every import of every descendant into the bundle.
The minimal fix
"use client";
import { useState } from "react";
export function Counter() {
const [n, setN] = useState(0);
return setN(n + 1)}>{n};
}
The directive applies to the file, not to the component. If the file imports a large library, that library enters the client bundle, which is why the directive belongs as low as possible.
The better fix: a small boundary
// app/page.tsx — Server Component
import { Counter } from "./counter";
export default async function Page() {
const total = await getTotal();
return ;
}
The Server Component loads the data and passes a serialisable value. The Client Component handles the interaction. The boundary is one file wide and the rest of the page stays on the server.
The diagnostic step
# files that call a hook but never declare the boundary
# Windows: run this in Git Bash
grep -rlE "use(State|Effect|Ref|Reducer)\(" app components | xargs grep -L "use client"
The list you get holds candidates, not culprits: a component may call a hook and receive its interaction from a client parent with no trouble. The culprit is the first file in the import chain printed by the error that also appears in that list. Start there rather than at the page: if the file is a presentational leaf, move the hook into a wrapper and leave the leaf stateless.
When the fix does not work
"use client" does not make children client: a Server Component passed as children into a Client Component stays on the server. If the hook still fails, the file using it is not the one you marked, or the component comes from a package that never declares the directive. That case needs a local client wrapper.
A second common mistake is passing a function as a prop to a Client Component: functions are not serialisable. Pass data instead, or use a Server Action for the mutation.
When the usual fix makes it worse
The most copied shortcut puts the directive at the top of page.tsx or layout.tsx. It seems to work while the page stays synchronous: as soon as there is an await, a client component cannot be async and the error changes shape instead of disappearing. In the same move the whole tree under that file becomes client, so every imported library lands in the bundle, and you pay for it in production, where no warning reminds you of it.
A named limitation
The documented limit: a module with "use client" cannot export metadata or generateMetadata. If you move the directive into the page to make a hook work, the title and description stop being generated on the server, and no rendering test notices. The way out is to keep the page as a server component and move only the interaction into a child.
How to prevent it
- Put
"use client"only in files with state, effects or events. - Keep presentational components hook-free: they stay on the server and add nothing to the bundle.
- Check the client bundle size after each new boundary, not at the end of the project.
- When a third-party package declares no directive, wrap it locally instead of marking its caller.
Where to go next
In the catalogue: components, sidebar with state and blocks. The guides: App Router, structure and boundaries and the weight of a UI library; the comparison: copy-paste or headless.
Frequently asked questions
Why does the error name useState when I use useEffect?
React reports the first hook it meets in the module. The problem is the same: the file is not a Client Component.
Does "use client" make the children client too?
No. Children passed as children from a Server Component stay Server Components, because the boundary applies to the module that declares it.
Can I pass a function to a Client Component?
No, props must be serialisable. Pass data, or use a Server Action for the mutation.
Why did the bundle grow after "use client"?
The boundary pulls everything the module imports into the client bundle. If the file imports a large library, that library ships to the client.
Does every file with an event need "use client"?
Yes, if the handler is defined there. If the handler comes from a client component already declared elsewhere, the file can stay on the server.