Most explanations of React Server Components start with where code runs. That framing produces endless confusion, because the interesting question is not where — it's what crosses the boundary.
The boundary is a serializer
A Server Component's output is not HTML and not JavaScript. It's a serialized description of UI — props and element trees — streamed to the client. The moment you internalize that, the rules stop being arbitrary:
- Props passed from server to client components must be serializable. Functions aren't. Dates survive; class instances don't.
- A
"use client"file marks an entry point into the client graph, not a per-component annotation. - Server Components can render Client Components, but not the other way around — a serialized tree can reference a client entry point, while a client bundle has no way to reach back into the server.
A rule of thumb that has never failed me
// Server by default. Reach for "use client" only when the component
// needs one of: state, effects, refs, browser APIs, event handlers.On this site, that means the theme toggle, the keyboard navigation, the typing animation, and the photo lightbox are client components — and almost nothing else is. The articles you're reading are rendered entirely on the server, shipped as HTML, and cost the client nothing.
Why this matters beyond performance
The serialization boundary is an architectural forcing function. It pushes
data access to the server (where secrets and databases live), and pushes
interactivity to the leaves of the tree. Codebases that fight this get
"use client" at the root and lose everything RSC offers. Codebases that
lean in get a clean layering for free.