Server Components
Date: 2026-08-19
Components that run on the server and never ship their code to the browser. Not server rendering — that sends the same components to run twice. These run once, on one side, and what reaches the client is the output plus a list of holes where interactive components go.
A server component executes only on the server, can await data directly, and sends a serialised description of its output to the client — never its source. A client component ships as JavaScript and runs in the browser, as components always have.
What actually crosses the wire
SERVER RENDERING (SSR) SERVER COMPONENTS
component code ──▶ HTML component code ──▶ output description
│ │
└──────────▶ same code └── stays on the server. never sent
sent to the
browser to only components marked as client
hydrate are sent, with their props
↓ ↓
bundle contains everything bundle contains the interactive parts only
The output is not HTML. It’s a serialised tree — text, element descriptions, and references to client components with their props — which the client framework merges into its own tree. That matters because a re-fetch can update a server-rendered section without discarding the client state sitting inside it; sending HTML couldn’t do that.
The rules that follow from the boundary
Everything awkward about server components is a consequence of one fact: the two halves are separated by a network, and only serialisable values cross it.
- Server components can’t hold state or effects. They run once, produce output, and are gone. No
useState, no event handlers - Props passed to a client component must be serialisable. Objects, strings, numbers, arrays — not class instances, and not arbitrary functions. The one exception is a server function: a function explicitly marked as server-invocable, which crosses as a reference the client can call back over the network
- Client components can’t import server components, but can receive them as children. This is the part that surprises people, and it’s why layouts stay on the server while their interactive shells don’t
- Server code can be imported freely — a database client, an API key, a 200KB markdown parser — because none of it ships
That last one is the actual prize. A date-formatting library used once in a footer stops costing the user anything at all.
What it buys, and what it costs
| Buys | Bundle size that no longer grows with content code · data access without an API layer · secrets usable in component code · less Hydration work, because inert components never hydrate |
| Costs | A round trip for anything the server must re-decide · a boundary you have to hold in your head on every file · debugging that spans two runtimes · framework lock-in, since the protocol is the framework’s |
The failure mode is boundary drift. One component needs an onClick, so it’s marked client; everything it imports is now client too, transitively. Repeat for a few months and the bundle is back where it started, without anyone making a decision. The countermeasure is structural: push client boundaries down and out to the leaves — a client <AddToCart> button inside a server product page, rather than a client page that has to take everything else in as children.
The mental model that makes it click
Think of the page as static output with interactive holes punched in it, which is the same premise as Islands Architecture — the difference is that islands are declared by the author at the page level, while server components draw the boundary per component and stitch the result into a single tree.
server ─ Layout
└ server ─ ProductPage data fetched here, code stays here
├ server ─ Description
├ client ─ VariantPicker ← ships, hydrates
└ client ─ AddToCart ← ships, hydrates
Where this sits
Server components are a rendering architecture, not a data-fetching one, though they change how fetching feels: awaiting directly in a component is fine when the component runs on the server, and reintroduces the waterfall from Data Fetching Patterns the moment those awaits nest. The parallelism problem doesn’t go away; it moves.
Worth knowing rather than adopting reflexively. For a content-heavy site with small interactive pockets, the bundle argument is strong. For an application that is mostly interactive, the boundary bookkeeping buys much less — and Rendering Strategies is the prior decision either way.