You add use client to silence a hook error, and suddenly half your app is shipping to the browser. That’s the trap almost everyone hits with Server Components. The fix isn’t a new API — it’s a new default in your head: the server renders, the client only reacts.
Server by default
In the App Router, every component is a Server Component until you say otherwise. It runs once, on the server, and ships zero JavaScript to the user.
You get a direct line to your database and secrets that never touch the client. Most of your tree should live here — layouts, pages, anything that just reads data and prints markup.
Because it renders once and then disappears, there’s no re-render, no effect cleanup, nothing to hydrate. The HTML arrives finished.
Client islands
Reach for use client only when a component needs state, an effect, an event handler, or a browser API. Picture those as small islands in a server-rendered sea. A button with onClick, a form with local state, a widget that reads window — each is its own leaf.
Here’s the one that bites everyone. You drop use client at the top of a page so a single dropdown works, and now the page plus everything it imports ships to the browser. Push the directive down to the smallest component that truly needs it.
The cost is cumulative. A client component drags in its imports, and their imports, all the way down. That’s the difference between a 40KB page and a 300KB one.
Where data lives
Fetch data in Server Components, right where it renders. Write an async function, await the query, return JSX — no useEffect, no loading flag, no client-side waterfall. Then pass the result down as props.
Need the same data in two components? Fetch it in both. React dedupes identical requests within a render, so there’s no context or prop-drilling to wire up.
// Server Component — no directive needed
import { LikeButton } from "./like-button";
export default async function Page() {
const post = await db.post.find(id); // runs on the server
return <LikeButton postId={post.id} likes={post.likes} />;
}The rule
Start every component on the server. Add use client only when the build forces you, and add it to the leaf, not the layout.
If a component just displays props, it stays put. When you’re unsure, ask one question: does this need to run in the browser? If the honest answer is no, it belongs on the server.
Get that one reflex right and your bundle shrinks without you thinking about it.