React Server Components (RSCs) changed how the React team thinks about rendering — but the concept is often explained in a way that's more confusing than it needs to be. Here's the practical version.
The problem RSCs solve
Before Server Components, a typical React app shipped one thing to the browser: JavaScript. Every component — even ones that only fetched data and rendered static markup — had to be bundled, downloaded, and hydrated on the client.
That's wasteful for components that never need interactivity. A blog post body, a product description, a footer — none of that needs to run in the browser at all.
Server Components let you write components that:
- Run only on the server (or at build time, in a static export)
- Never ship their JavaScript to the client
- Can directly access server-only resources (the filesystem, environment secrets, databases) without an API layer
Server vs. Client Components
// app/components/PostBody.tsx — Server Component (default)
export default async function PostBody({ slug }: { slug: string }) {
const post = await getPostBySlug(slug); // runs on the server / at build time
return <article dangerouslySetInnerHTML={{ __html: post.html }} />;
}// app/components/LikeButton.tsx — Client Component
"use client";
import { useState } from "react";
export default function LikeButton() {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>{liked ? "Liked" : "Like"}</button>;
}The "use client" directive is the boundary. Everything below it (and its imports) gets bundled for the browser. Everything without it stays on the server.
When to reach for each
| Use a Server Component when... | Use a Client Component when... |
|---|---|
| You're fetching data | You need useState or useEffect |
| The content is static per request | You're attaching event listeners |
| You want zero client JS for that piece | You depend on browser-only APIs |
A common mistake
A frequent anti-pattern is slapping "use client" on a top-level layout "just to be safe." That drags every child component into the client bundle, even ones that didn't need it. Push "use client" as far down the tree as possible — ideally onto small, leaf components like a button or a form.
Static export and Server Components
In a fully static export (output: "export"), Server Components still work exactly as described — they just run once, at build time, instead of per-request. The output is plain HTML, so there's no runtime cost at all for visitors.
That's the sweet spot for a content site like this one: nearly everything is a Server Component, and "use client" is reserved for the handful of pieces that truly need interactivity — theme toggles, search boxes, and copy buttons.