Most confusion about React Server Components comes from trying to fit them into an old mental model — "it's SSR", "it's PHP again", "it's a data-fetching library". None of those quite fit, and the gaps are where teams get hurt. Here is the model we teach, and the practical rules that fall out of it.
The model: two programs, one tree
An RSC application is two programs that cooperate to produce one React tree.
The server program runs at request time (or build time). Its components can be async, can read databases and file systems directly, and render to a serialized description of UI — not HTML, but a stream describing elements and where client components plug in. Server components never re-render on the client; there is no such thing as a server component with state.
The client program is the React you already know. Client components hydrate, hold state, run effects, and re-render on interaction. They are marked with 'use client', and — this is the part people miss — the directive marks a module boundary, not a single component. Everything imported from a 'use client' module, and everything those modules import, ships to the browser and runs there.
The tree can interleave: server components render client components, and client components can receive server-rendered subtrees — but only as props (typically children). A client component can never import a server component, because importing would pull server code into the browser bundle.
What follows from the model
Rule 1: The boundary is the unit of design. The question is never "should this component be server or client" in isolation — it is "where do the boundaries go". Push boundaries as low in the tree as possible: a page that needs one interactive dropdown should be a server page containing a small client <Dropdown>, not a page-wide 'use client'. Every boundary you draw high drags everything below it into the bundle.
Rule 2: Serialization is the contract. Props crossing the server→client boundary must survive serialization: plain objects, arrays, strings, numbers, dates, promises — not class instances, not functions (except server actions). If you find yourself wanting to pass an ORM entity to a client component, the design is telling you to map it to a plain shape first, which is a good idea anyway.
Rule 3: Data fetching moves to where the data is. A server component that needs data just awaits it. This eliminates the classic SPA waterfall (load bundle → render → fetch → render), and it changes what your client data library is for: with RSC, libraries in the react-query mold are for data that changes after load — polling, optimistic mutation, infinite scroll — not for the initial read.
Rule 4: Server components are not SSR. SSR renders your client program to HTML once, then hydrates all of it. Server components never hydrate — their output is data in the stream, their code never ships. The two compose: SSR gives you fast first paint of client components; RSC removes whole subtrees from the client bundle entirely. An app can and usually does use both.
Where teams go wrong
The page-level 'use client'. Under deadline, someone needs useState in a page and marks the whole page client. It works — and silently forfeits every RSC benefit for that route. Watch for this in review; the fix is almost always extracting the stateful fragment into its own small module.
Interleaving via import instead of props. A client layout that imports a server data component will not error in the way you expect — the imported component just becomes a client component, its data code bundled or broken. If a client component needs server-rendered content inside it, accept it as children.
Treating server components as free. Server rendering costs server time and happens per request. An expensive computation in a server component is still expensive; it just bills your infrastructure instead of the user's phone. Cache accordingly.
Reaching for RSC because it is new. A logged-in dashboard that is 90% interactive widgets gains little from server components and pays real architectural complexity for them. A content-heavy product — commerce, media, docs, marketing — with islands of interactivity is the shape RSC was built for. Be honest about which one you have.
How to adopt it
Do not convert an app wholesale. Pick one route with a clear shape — heavy content, light interactivity — and build it with real boundaries: server page, small client leaves, props-only interleaving. The mental model clicks fastest when a team ships one route and inspects what actually landed in the bundle and the stream. After that, the boundary questions in review answer themselves — and the boundaries are the whole game.