React Server Components in Depth
Why Server Components shrink the client bundle, how they solve the data-fetching waterfall problem, and how they compose with Client Components.

What React Server Components Actually Are
React Server Components (RSC) are a component type that renders ahead of time, in a server environment — separate from the client application and separate from any SSR server. That render happens either once at build time or per request on the server, before anything gets bundled.
The Key Difference from Client Components
The defining constraint of a Server Component is what it can't do: no useState, no useReducer, no useEffect, no access to localStorage or window, no event handlers like onClick. In exchange, it can await directly in its own body:
// A valid Server Component
async function Notes() {
const notes = await db.notes.getAll();
return <div>{notes.map(n => <p key={n.id}>{n.content}</p>)}</div>;
}
Why It Matters: Smaller Bundles
Say you need to parse and sanitize markdown. Without Server Components, you'd ship libraries like marked and sanitize-html — roughly 75KB gzipped combined — straight to the client bundle. With RSC, those libraries run only on the server and never reach the browser at all; the client just receives the final HTML.
Solving the Waterfall Problem
Without Server Components, fetching related pieces of data usually cascades: one component's fetch has to finish before the next one can even start. Because a Server Component can reach a database or service directly, those fetches can run in parallel instead — with no state and no useEffect required.
Security as a Side Benefit
Because a Server Component runs directly on the server, it can talk to a database without exposing any credentials to the client. Tokens and API keys never leave the server, and access control can be enforced at render time itself.
Composing with Client Components
A Server Component can render a Client Component as a child, blending server-rendered output with client-side interactivity:
// Server Component
async function Notes() {
const notes = await db.notes.getAll();
return notes.map(n => <Expandable key={n.id}><p>{n.content}</p></Expandable>);
}
// Client Component
'use client';
function Expandable({ children }) {
const [open, setOpen] = useState(false);
return <div onClick={() => setOpen(!open)}>{open && children}</div>;
}
The browser only downloads the Expandable bundle — the Notes component's code and its database access never reach the client at all.
Limitations
A Server Component can't create new Context (it can only render a Provider defined in a Client Component), and per React's own documentation, the underlying RSC bundler APIs aren't yet semver-compliant — meaning frameworks need to pin their React version carefully.
Takeaway
Server Components represent a real shift in how rendering works: combining the simplicity of a server request/response model with the interactivity of a modern app, cutting redundant fetches, shrinking bundle size, and giving components secure, direct access to the backend.



