
Next.js use client
Every component in a Next.js App Router project starts life as a Server Component. That's the default, and it's a good one — it means your components run on the server, never ship their code to the browser, and can talk to a database or the filesystem directly. But the moment you need useState, an onClick handler, or window, you need a way to say "this part actually has to run in the browser." That's what 'use client' is for.
It looks like a tiny, almost cosmetic piece of syntax — a string literal sitting at the top of a file. In practice it's one of the most consequential lines you can write in a Next.js app, because it doesn't just flip a switch on one component. It draws a line in your module graph, and everything downstream of that line gets treated differently by the bundler, the server, and the browser. Get it in the wrong place and you can quietly balloon your JavaScript bundle or break server-only code without a single error message telling you why.
This article is a focused reference on the directive itself: where it goes, what it actually marks, how props cross the boundary it creates, and the mistakes that trip people up once a codebase grows past a toy example.
What 'use client' Actually Marks
The single most important thing to understand is that 'use client' does not mark a component. It marks a module — a file — as an entry point into the client bundle.
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Once you put that directive at the top of counter.tsx, before any imports, every component this file exports becomes part of the client's module graph. When a Server Component imports Counter and renders it, React doesn't execute Counter's function body on the server at all — it renders a reference to it, and the browser is the one that actually runs the code and produces the DOM.
The directive has to be the very first line of the file (comments aside). Put it after an import and it simply won't work as a directive — bundlers like Turbopack and webpack look for the string literal as the first statement, not somewhere buried in the file.
The File Is the Boundary, Not the Component
This is the detail that catches people out: 'use client' doesn't just affect the default export you had in mind when you added it. It affects the whole file, and — this is the part the docs page understates — it affects the import graph rooted at that file in a specific direction.
Concretely:
- Every component exported from a
'use client'file becomes a Client Component, whether you meant to convert it or not. - Anything that file imports — a utility function, a hook, a small helper component you didn't bother splitting out — gets pulled into the client bundle too, even if that imported code never touches
useStateor the DOM.
That second point is the one that quietly grows bundle sizes over time. If your 'use client' file imports a date-formatting utility from a shared lib/format.ts, that formatting code ships to the browser now, even though it's perfectly capable of running on the server. The directive doesn't ask "does this specific function need the browser?" — it asks "is this file downstream of a client boundary?" and treats everything downstream as client code by default.
The practical implication: keep 'use client' files as small and as low in your component tree as you can. A 'use client' directive at the top of your root layout effectively makes your entire application a client-rendered app with extra steps, because nearly everything imports from somewhere near the root.
Serializable Props Across the Boundary
When a Server Component renders a Client Component, it isn't calling a JavaScript function directly — it's serializing the props into the React Server Components (RSC) payload, sending that payload to the browser, and letting React reconstruct the Client Component with those props on the client side. That means props crossing from server to client have to be serializable.
"use client";
export default function Counter({
onClick /* ❌ Function is not serializable */,
}) {
return (
<div>
<button onClick={onClick}>Increment</button>
</div>
);
}
Plain functions defined on the server can't cross this boundary, because there's no way to represent "run this exact server-side closure" as JSON-like data sent over the wire. Strings, numbers, plain objects, arrays, and a handful of special React-aware types (like Promises passed to use(), or Server Actions) are fine. Arbitrary callbacks, class instances, Dates in some configurations, and functions are not.
This is also why Server Actions look like an exception to the rule but aren't really one — a function passed from a Server Component to a Client Component only works if it's specifically marked with 'use server', because Next.js and React give that kind of function a special serializable reference (essentially, an ID the client can call back to the server with) instead of trying to send the function itself.
If you hit a "functions cannot be passed directly to Client Components" error, the fix is almost always one of: turn the callback into a Server Action, move the interactive logic into the Client Component itself, or restructure so the Client Component only needs data, not behavior, from its parent.
Composing Server and Client Components
The directive isn't an all-or-nothing switch for your app — it's a tool for drawing boundaries exactly where you need interactivity and nowhere else. The recommended shape:
- Server Components for anything static: data fetching, layout structure, SEO-relevant markup, content that doesn't change based on user interaction.
- Client Components for anything that needs state, effects, event handlers, or browser-only APIs.
- Composition — nest the Client Components inside the Server Components, rather than converting whole subtrees.
// app/page.tsx — Server Component (no directive)
import Header from "./header";
import Counter from "./counter"; // This is a Client Component
export default function Page() {
return (
<div>
<Header />
<Counter />
</div>
);
}
Header stays a Server Component, rendered on the server and never shipped to the client as JavaScript. Counter is the one interactive island, and only its code (plus whatever it imports) crosses the boundary.
The children-as-slot pattern
A subtlety worth calling out explicitly, since it's easy to get backwards: importing a Server Component into a Client Component file does not make that Server Component run on the client. What actually happens is more restrictive — you generally can't import a Server Component into a 'use client' file and render it directly, because by the time you're inside client code, you no longer have access to the server-only rendering context that Server Components rely on (direct database calls, cookies(), headers(), and so on).
The pattern that works is to keep the composition happening in a Server Component, and pass the already-rendered Server Component down as children (or another prop) into the Client Component:
// app/layout.tsx — Server Component
import ClientWrapper from "./client-wrapper";
import ServerSidebar from "./server-sidebar";
export default function Layout({ children }: { children: React.ReactNode }) {
return <ClientWrapper sidebar={<ServerSidebar />}>{children}</ClientWrapper>;
}
// app/client-wrapper.tsx
"use client";
export default function ClientWrapper({
children,
sidebar,
}: {
children: React.ReactNode;
sidebar: React.ReactNode;
}) {
return (
<div className="layout">
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
);
}
ClientWrapper never imports ServerSidebar directly — it just receives its already-rendered output as a prop. The server does the work of rendering ServerSidebar, and the client only has to slot that finished output into place. This is the mechanism that lets you wrap interactive client-side chrome (a sidebar toggle, a modal shell, a theme provider) around content that's otherwise entirely server-rendered, without dragging that content into the client bundle.
What Actually Ends Up in the Client Bundle
Because the directive works at the file level and follows imports downward, it's worth being deliberate about auditing what's actually shipping to the browser rather than assuming. A few practical ways to check:
Read the build output. next build prints a route-by-route breakdown of first-load JS size. A route that suddenly balloons is a strong hint that a 'use client' boundary crept somewhere it shouldn't have.
Use the bundle analyzer. @next/bundle-analyzer gives you a visual treemap of what's actually in each client chunk — it's the fastest way to spot a large server-only dependency (a date library, a markdown parser, a validation schema) that accidentally got pulled client-side through an import chain.
Grep for the directive. In a codebase that's grown organically, running grep -rl "'use client'" app/ and looking at where those files sit in the tree tells you a lot. If you find 'use client' near the top of your route tree — a layout, a top-level page — that's usually a sign the boundary should be pushed further down into a smaller, more specific component.
Common Mistakes
Putting the directive too high. The single most common issue: someone needs one small interactive widget on a page, adds 'use client' to the whole page component to make the import work, and inadvertently converts everything else on that page into client-rendered code too. The fix is almost always to extract just the interactive piece into its own small component and mark only that file.
Assuming imports stay server-side. A 'use client' file that imports a "pure" utility function doesn't keep that utility on the server just because the utility itself doesn't use any browser APIs — the bundler doesn't do that kind of analysis. If the util is imported from a client file, it ships to the client.
Forgetting it's per-file, not per-export. If a file exports five components and you only meant one of them to be interactive, adding 'use client' still converts all five. Split unrelated components into separate files if some genuinely don't need to be client-side.
Trying to import a Server Component directly into client code. This one produces a confusing error rather than a silent bug. If you need server-rendered content inside a client subtree, pass it down as children or another prop from a parent Server Component instead of importing it directly.
Passing non-serializable props out of habit. Passing a callback down "just in case" from a Server Component to a Client Component is a common source of the serialization error. If the Client Component needs to trigger server-side work, reach for a Server Action ('use server') rather than a plain function reference.
Key Takeaways
| Question | Answer |
|---|---|
What does 'use client' mark? | The file it's in as an entry point to the client bundle — not just one component |
| Where does it have to go? | The very first line of the file, before any imports |
| Does it need to be on every Client Component file? | No — only on the files that serve as entry points; components a Client Component imports don't each need their own directive |
| What can cross from Server to Client as props? | Serializable values — strings, numbers, plain objects/arrays, Server Actions — not plain functions or class instances |
| Can a Client Component import a Server Component directly? | No — pass the rendered Server Component down as children or a prop instead |
| Biggest practical risk? | Placing the directive too high in the tree, silently pulling large amounts of otherwise server-only code into the client bundle |
The directive itself is one line, but the mental model behind it — "this file and everything it imports downward is now client code" — is what actually matters day to day. Get the boundary as small and as low as it can be, let Server Components handle everything static, and reach for 'use client' only where interactivity genuinely requires it.


