You're importing a component that needs useState. It only works in a Client fires the
Component, but none of its parents are marked with "use client"
moment a Server Component's render tree reaches a hook call with no
"use client" boundary above it. The message names useState, but the same
error fires identically for useEffect, useReducer, useContext, and any
other hook — React hooks require a component instance that lives in the
browser, and Server Components never do.
The one-line fix — and why it's often in the wrong file
The fix is adding "use client" as the very first line of the file that
calls the hook:
// components/Counter.tsx
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}
The mistake this error usually hides is adding the directive to the file that
imports Counter, not the file that defines it:
// app/page.tsx
'use client'; // ❌ wrong file — Counter still lacks its own directive
import { Counter } from '@/components/Counter';
export default function Page() {
return <Counter />;
}
This "fixes" the error because now the whole page is a Client Component, so
Counter's missing directive never gets checked. But it silently converts
every Server Component in that subtree — data fetching, async/await
calls, direct database access — into client-rendered code, which is the exact
performance and bundle-size regression the App Router exists to avoid. "use marks a boundary, not a page-wide switch: put it on the leaf
client"
component that actually needs interactivity, and everything it imports
becomes part of the client bundle, but everything that imports it does not.
The barrel-file version of this bug
The same error appears when the hook-using component is fine, but it is
re-exported through an index file with no directive:
// components/index.ts — barrel file
export { Counter } from './Counter';
export { Header } from './Header';
// app/page.tsx — Server Component
import { Counter } from '@/components'; // through the barrel
If Counter.tsx itself has "use client" this works — the directive lives
with the component, and re-exporting does not strip it. The failure mode to
watch for is a barrel file that imports the hook and forwards a wrapped
version, which does need its own directive at the top of the barrel:
// components/index.ts
'use client';
export function CounterWithLabel(props) {
return <Counter {...props} />; // now this file defines new render logic
}
When a third-party component triggers it
If the hook lives inside a library you cannot edit (a UI kit, a chart
package), wrap it in your own Client Component instead of trying to patch the
library:
// components/ChartClient.tsx
'use client';
import { InteractiveChart } from 'some-chart-lib';
export function ChartClient(props: React.ComponentProps<typeof InteractiveChart>) {
return <InteractiveChart {...props} />;
}
// app/page.tsx — Server Component
import { ChartClient } from '@/components/ChartClient';
export default async function Page() {
const data = await getChartData();
return <ChartClient data={data} />;
}
Data fetched on the server passes through as a plain prop; only the rendering
that needs the hook lives on the client.
Verifying the fix
-
grep -rln "useState\|useEffect\|useReducer\|useContext" src components appand check each file for"use client"as its literal first line — a directive after an import statement is ignored. - Confirm you added the directive to the smallest possible component, not to
layout.tsxorpage.tsx— check withnext buildthat unrelated Server Components in the same file tree still run without a browser bundle. - If using a barrel file, verify the directive is on the file that actually contains the hook, not assumed to propagate through re-exports of files that already have it (re-exports are fine; new logic in the barrel is not).
Related Incidents
- "use client" — Event handlers cannot be passed to Client Component props
- Fix: NextRouter was not mounted
- React Server Components Deep Dive
- Fix useEffect Running Twice in React 18 — Strict Mode
- Next.js Hydration Mismatch: 8 Fixes for App Router (2026)
Originally published at https://www.iloveblogs.blog
Top comments (0)