Using "use client" Effectively
Where to place the use client boundary so you don't accidentally turn your whole app into a client bundle.
អាន 3 នាទី
Knowing that "use client" exists is easy. Knowing where to put it is what actually determines how much JavaScript your app ships — put it in the wrong place and you can accidentally convert most of your page into client-rendered code without realizing it.
The directive marks a boundary, not a single component
Once a file has "use client" at the top, every component it renders directly and everything it imports becomes part of the client bundle too:
// app/ui/search.tsx
"use client";
import { useState } from "react";
import { HighlightMatch } from "./highlight-match"; // also bundled to the client
export default function Search() {
const [query, setQuery] = useState("");
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
);
}HighlightMatch doesn't need its own "use client" directive — it inherits client status simply by being imported into a client file. This is why the placement of the boundary matters so much more than whether any individual component "needs" it.
Push the boundary as low as possible
The common mistake is marking an entire page or layout as a Client Component because one piece of it — a search box, a dropdown — needs interactivity. Instead, keep the page as a Server Component and carve out just the interactive part:
// app/layout.tsx — stays a Server Component
import Search from "./ui/search"; // Client Component
import Logo from "./ui/logo"; // Server Component
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<nav>
<Logo />
<Search />
</nav>
<main>{children}</main>
</>
);
}Logo and everything in <main>{children}</main> stay server-rendered with no client JavaScript cost. Only Search — and whatever it imports — ships to the browser.
Server Components can't be imported into Client Components
The inheritance rule only runs one direction. A Client Component's module graph — its imports — become client code, but you can't import an async Server Component directly into a Client Component and expect it to work the same way. Instead, pass the Server Component in as children or another prop from a parent Server Component:
// app/ui/modal.tsx — Client Component providing a "slot"
"use client";
export default function Modal({ children }: { children: React.ReactNode }) {
return <div className="modal">{children}</div>;
}// app/page.tsx — Server Component composing them
import Modal from "./ui/modal";
import Cart from "./ui/cart"; // a Server Component that fetches cart data
export default function Page() {
return (
<Modal>
<Cart />
</Modal>
);
}Cart still renders on the server — it's passed to Modal as already-rendered output, not imported into Modal's own module graph. This children-slot pattern is the standard way to keep server-rendered data fetching working inside an interactive client shell like a modal or an accordion.
A rule of thumb
Ask "does this specific piece need state, an event handler, or a browser API?" If yes, that piece gets "use client". If you're tempted to add the directive just because a Client Component is inside this file somewhere, look for a way to pass it in as children instead.