00 RSC · Flight · HTML · from first principles
One request starts one server render.
It produces two things the browser needs — and the order is strict.
A React Server Components app does not turn your tree into plain HTML and forget it. One render produces a Flight payload — the data model of the Server Component tree — and a stream of HTML the browser paints first. They can be produced at overlapping times, but the dependency never reverses: HTML is downstream of the server-rendered React result. Press play and follow one request all the way to an interactive page.
Press play to follow the dependency chain
Violet runs on the server. Amber is the Flight model. Cyan is HTML the browser paints. Green is the interactive layer. Each stage feeds the next — top to bottom is literally "downstream".
01 One source, a strict dependency
Flight is the model · HTML is rendered from itHTML and Flight are not two unrelated outputs
Flight is the React data model for the Server Component render. The HTML stream is produced from the renderable result of that same model. They may be produced and streamed at overlapping times, but the dependency is one-directional.
Server Components run ↓ Flight rows are produced ↓ React DOM Server uses the result to produce HTML ↓ the browser paints the HTML ↓ the browser uses Flight + client JS to hydrate
Read that top to bottom: every line depends on the one above it. The server can stream — it does not finish the whole tree before emitting anything — but it can never emit the HTML for a region before it has produced the React result for that region. That single fact is the spine of the whole pipeline.
<Counter> client boundary with these props." Flight can.
02 The subtle part
streamed in parallel · still strictly orderedOverlap in time is not the same as independence
Two facts about this pipeline sound like they contradict each other. The server streams — it flushes HTML for finished regions while slower regions are still rendering, so work overlaps in time. And yet the order is strict — a region's HTML can never appear before that region's Flight. Both are true, because they describe two different axes.
Look across the whole page and you see concurrency: at a single instant the server can be producing region C's model while the browser is already painting region A. Now zoom into any one region and you see a strict queue: its Flight rows first, its HTML second — always.
"Overlap" is a statement about different regions at the same time. "Dependency" is a statement about one region's two artifacts. Picture a single global timeline and the two collapse into a contradiction. Keep the axes apart and it's obvious.
the truth sits between two tidy but wrong models
Press play to watch Flight gate the HTML
The top lane is Flight rows the server produces. The bottom lane is HTML the browser receives. An HTML region only turns solid once its Flight row exists above it; until then it is a fallback.
03 Three artifacts, three jobs
don't conflate themHTML, Flight, and client JS each answer a different question
The browser receives three things from one render. They overlap in content — they describe the same UI — but each exists for a different consumer. Tap each to see the question it answers.
HTML is the immediate, non-interactive structure for the first frame. The browser parses it and paints pixels with no JavaScript required. It is the answer to "what does the user see right now," and nothing more — it carries no notion of which parts are React components or where the client boundaries are.
04 The first thing that happens
execute on the server · serialize to FlightServer Components run first
A Server Component is a function the server calls. It can fetch data, read the filesystem, call server-only code, or await async work. React turns its returned tree into the Flight payload — not into component code sent to the browser.
app/page.tsx · a Server Component
// app/page.tsx export default async function Page() { const posts = await db.posts.findMany() return ( <main> <h1>Posts</h1> <PostList posts={posts} /> </main> ) }
The server calls Page(), which returns React elements. That returned JSX is not sent to the browser as component code. Instead React encodes it as the Flight payload, which contains:
- rendered Server Component output - serialized props - placeholders for suspended async work - references to Client Component modules
05 The most common confusion
Server Components produce HTML tooServer Components are not missing from the HTML
It is tempting to think Server Components only become Flight while Client Components are what become HTML. That is not right. A Server Component can absolutely produce visible UI — it simply does not ship JavaScript for it.
"Server Components only become Flight. Client Components are what become HTML."
This makes you imagine Server Components vanishing from the page — as if anything you can see must have shipped JavaScript. It leads people to "optimize" by deleting server-rendered markup, or to assume a static page needs a client bundle to appear.
app/page.tsx · a pure Server Component, no client code
export default function Page() { return ( <main> <h1>Hello from the server</h1> <p>This is a Server Component.</p> </main> ) }
There is no Client Component here. The server runs Page(), and React DOM Server emits ordinary HTML for it — <main><h1>Hello from the server</h1>…. No browser JavaScript is needed for that markup to appear. So the clean rule is: Server Component JSX runs on the server, is included in the server-rendered result, and can become HTML. It just does not ship component JavaScript.
06 Where the client begins
"use client" marks a boundaryClient Components appear as boundaries
Add a Client Component and React cannot treat it like a Server Component — the "use client" directive marks it. The server records a reference to it, server-renders its initial HTML so the page is not blank, and the browser brings it to life later.
app/page.tsx · Server Component
import Counter from "./Counter" export default function Page() { return ( <main> <h1>Dashboard</h1> <Counter initialCount={0} /> </main> ) }
app/Counter.tsx · Client Component
"use client" import { useState } from "react" export default function Counter({ initialCount }) { const [count, setCount] = useState(initialCount) return <button onClick={() => setCount(count + 1)}> {count} </button> }
Press play to trace one client boundary
On the left the server discovers the boundary, records a module reference plus props, and renders the button's initial HTML. On the right the browser paints it, downloads the module, and hydrates it into a live control.
<button>0</button> came from server-rendered HTML. The click handler came from the client bundle, attached during hydration. A Client Component is pre-rendered to HTML on the server and ships JS to the browser — both are true.
07 What React DOM Server emits
one streamed response · HTML + inlined FlightOne response can contain both HTML and Flight
React DOM Server does not only render Client Components to HTML. It produces HTML for the renderable result of the whole tree — Server Component output, server-rendered Client Component markup, Suspense placeholders, and the marker scripts that fill streamed regions later. On the first load, that HTML arrives in the same response as the inlined Flight data.
Press play to stream one response
Cyan blocks are visible HTML. Amber blocks are inlined Flight pushed into self.__next_f. They interleave in one response — not "HTML now, Flight in a second request."
self.__next_f). Later navigations can fetch only the RSC payload without a full HTML document.
08 Two milestones, not one
visible before interactiveThe browser paints first, then hydrates
When the response arrives, the browser can paint the HTML before the page is interactive. Client JS loads, React reads the inlined Flight data, client modules load, and hydration attaches event listeners. A page can be visible long before it can respond to a click.
Press play to separate paint from interactivity
The viewport fills with grey, paintable HTML first — that is First Contentful Paint. Only after JS loads and hydration runs do the controls light up — Time to Interactive.
FCP
first contentful paintThe user can see something. Real markup from the server, painted with no JavaScript.
TTI
time to interactiveThe relevant client JavaScript has loaded and hydrated. Now clicks do something.
09 The difference that actually matters
by capability, not by artifactServer vs Client — the real split
The useful contrast is not "Server Components produce Flight, Client Components produce HTML." Both can produce HTML. The real difference is what each can do and what each ships.
Server Component
runs on the serverClient Component
pre-rendered on the server, lives in the browser"use client"What "React DOM Server walks the tree" really means
In older SSR you could imagine React walking the tree, calling every component the same way, and turning the result into HTML. In an RSC app the tree is split by capability — React does not treat every component the same. Tap a branch.
Is this a Server Component or a Client Component?
React asks one question of each node — does it carry "use client"? — and routes it down one of two very different paths.
10 Lock it in
the one rule · and what it correctsHTML is downstream of the server-rendered React result
The two streams can overlap in time, but they are not independent. There is no need to imagine Server Components disappearing from HTML — they are part of the server-rendered output. They just do not become JavaScript components in the browser.
Four myths this corrects
Each card is a tidy mental model that quietly breaks. Tap one for the correction.