The Journal

React & Performance3 min read

Next.js Server Components: The Game-Changer You Need to Know About

Server Components are changing everything about how we build React apps. Here is how to use them properly and why they are so much better than the old way.

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.

app/post/page.tsx
// 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.

Frequently asked questions

When should I use the use client directive in Next.js?
Add use client only when a component needs state, an effect, an event handler, or a browser API; everything else stays a Server Component. Push the directive down to the smallest leaf that needs it, because it forces that component and everything it imports to ship to the browser.
How do I fetch data in React Server Components?
Fetch data directly inside a Server Component by writing an async function, awaiting the query, and returning JSX, with no useEffect or loading flag. React dedupes identical requests within a render, so you can fetch the same data in two components without context or prop-drilling.
Why do Next.js Server Components make my bundle smaller?
A Server Component runs once on the server and ships zero JavaScript to the browser, so most of your tree costs nothing to hydrate. The bundle only grows for client islands, which is why misplacing use client on a page can turn a 40KB page into a 300KB one.