React Server Components (RSC) change where your components run and what ships to the browser. Used well, they cut bundle size and simplify data fetching. Used carelessly, they blur the client/server line. Let's demystify them.
The Mental Model
A Server Component renders on the server and never ships its code to the client. A Client Component is interactive and hydrates in the browser. The boundary is explicit — you opt into the client with a directive.
// Server Component by default — runs on the server, ships zero JS.
import { getPosts } from "@/lib/posts";
import { LikeButton } from "./LikeButton";
export default async function Page() {
const posts = await getPosts();
return posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<LikeButton postId={post.id} />
</article>
));
}Where the Boundary Lives
"use client";
import { useState } from "react";
export function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked((v) => !v)}>{liked ? "♥" : "♡"}</button>;
}Note
Props passed from a Server Component to a Client Component must be serializable — no functions, no class instances.
When to Reach for RSC
- Data-heavy pages where most of the tree is non-interactive.
- Reducing client bundle size on content-first routes.
- Keeping secrets and heavy dependencies on the server.
Key Takeaways
RSC is not "server-side rendering 2.0" — it's a new axis. Push interactivity to the leaves, keep the trunk on the server, and your app gets lighter almost for free.