Next.js error
You're Importing a Component That Needs useState
The error
You're importing a component that needs `useState`. This React hook only works in a client component. To fix, mark the file (or its parent) with the `"use client"` directive.The error suggests a fix and most people apply it to the wrong file. Which file gets the directive decides how much JavaScript your users download.
Every component in the App Router is a Server Component by default. Server Components render once, on the server, and never re-render - so they have no state, no effects, no event handlers and no hooks.
The error is correct and its suggested fix is correct. What it cannot tell you is which file to put the directive in, and that decision is the whole cost.
The directive marks a boundary, not a file
'use client' does not apply to one component. Everything that file imports,
and everything those files import, joins the client bundle with it.
So the two obvious placements have very different prices:
app/page.tsx 'use client' ← everything below ships
├─ <Hero /> ships
├─ <FeatureGrid /> ships
├─ <Testimonials /> ships
└─ <NewsletterForm /> ships (this is the one that needed useState)
app/page.tsx Server Component
├─ <Hero /> HTML only
├─ <FeatureGrid /> HTML only
├─ <Testimonials /> HTML only
└─ <NewsletterForm /> 'use client' ← only this ships
Same page, same behaviour, materially different First Load JS. The first version is what you get by putting the directive where the error pointed.
The fix
Extract the interactive part into its own file, mark that file, and leave the page a Server Component:
// app/newsletter-form.tsx
'use client';
import { useState } from 'react';
export function NewsletterForm() {
const [email, setEmail] = useState('');
// ...
}Then import it from the page as usual. A Server Component can render a Client Component; the reverse is what needs care.
Passing server data down
A Client Component can receive props from a Server Component, as long as they serialise. Data crosses; behaviour does not. If you need to pass something that is not plain data, that is a different error with a different fix.
You can also pass server-rendered markup through, which is the pattern people miss:
// Server Component
<Accordion> {/* 'use client' */}
<ExpensiveServerRenderedContent /> {/* stays on the server */}
</Accordion>Children are rendered before they reach the client component, so the wrapper can be interactive without dragging its contents into the bundle.
Measuring whether it mattered
next build prints First Load JS per route. Move a directive down a level,
build again, and compare - the number is the argument. In most codebases we
read, the boundary has drifted upward one accident at a time, and
what that costs is the same measurement across
every route.
