Server Components → Flight → HTML → hydrate
the exact order · what feeds what

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.

▼ request → Server Components → Flight → React DOM Server → HTML → paint → hydrate
00 / 00
STAGE 00 · —

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".

Server Components — run on the server Flight / RSC payload — the React model HTML — what the browser paints Client JS — hydration & interactivity
the early correction that fixes everything Server Components do not "only become Flight" while Client Components "become HTML." A Server Component produces visible HTML and Flight data; it just ships no component JavaScript. The split that matters is by capability, not by which artifact a component turns into.
2
outputs from one render
1
streamed response, first load
0 KB
JS a Server Component ships
2
milestones: paint, then interactive

01 One source, a strict dependency

Flight is the model · HTML is rendered from it

HTML 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.

why a model and not just markup The browser needs to know what React tree the painted HTML represents, so it can adopt that exact DOM during hydration instead of throwing it away and re-rendering. HTML alone cannot say "here is a <Counter> client boundary with these props." Flight can.

02 The subtle part

streamed in parallel · still strictly ordered

Overlap 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 analogy that makes it click A kitchen during a dinner rush cooks many dishes at once — dish A is being plated while dish B is still in the pan. That is the overlap. But no dish is ever plated before it is cooked. That is the per-dish order. Flight is cooking · HTML is plating · a region is a dish.

the truth sits between two tidy but wrong models

✕ too sequential “The whole Flight payload finishes, then HTML begins.” No — ready regions flush as HTML while later regions are still rendering.
✕ too independent “HTML and Flight are unrelated streams racing.” No — each HTML region strictly trails its own Flight rows.
✓ streamed and ordered Concurrent across regions, ordered within a region. The diagram shows exactly this — follow ① → ② and watch the gate.
▦ within a region: Flight → HTML (ordered) · across regions: overlapping in time
00 / 00
FRAME 00 · —

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.

the rule, in both halves Across regions, overlapping: the server keeps flushing whatever is ready, in any order. Within a region, ordered: ready Flight rows → that region's HTML can be produced; missing Flight rows → that region waits or shows a fallback. The streams overlap in time; the dependency never bends.

03 Three artifacts, three jobs

don't conflate them

HTML, 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 · for the browser's rasterizer

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 Flight

Server 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
streamed, not "finish then serialize" This is usually not a neat "finish the whole tree, then serialize it" phase. React can stream Flight rows as parts of the tree become ready: a ready Server Component emits its rows now, a still-waiting one emits a placeholder now and real rows later.

05 The most common confusion

Server Components produce HTML too

Server 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.

the wrong model

"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 boundary

Client 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>
}
▶ server records the boundary & initial HTML · browser downloads, hydrates, attaches onClick
00 / 00
STEP 00 · —

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.

initial pixels vs interactivity The initial <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 Flight

One 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.

▶ one HTTP response · the HTML shell, then chunks, then fallbacks replaced
00 / 00
FRAME 00 · —

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."

it is not a second request For the initial load it is closer to one streamed response with two parts: visible HTML, and inlined React/Flight instructions (in Next.js, script chunks such as self.__next_f). Later navigations can fetch only the RSC payload without a full HTML document.

08 Two milestones, not one

visible before interactive

The 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.

▶ blank → painted (FCP) → JS + Flight → hydrated (TTI)
00 / 00
FRAME 00 · —

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 paint

The user can see something. Real markup from the server, painted with no JavaScript.

TTI

time to interactive

The relevant client JavaScript has loaded and hydrated. Now clicks do something.

remember Painted HTML ≠ hydrated React UI. Decoupling the two is what makes a page feel instant even though interactivity arrives a beat later.

09 The difference that actually matters

by capability, not by artifact

Server 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 server
outputserver-rendered UI + Flight data
browser JSno component code
hydrationno
state / effectscannot use them
can dofetch data, read files, call server-only code

Client Component

pre-rendered on the server, lives in the browser
outputinitial HTML + a client reference
browser JSyes
hydrationyes
state / effectsstate, effects, events, refs, browser APIs
marked by"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.

▣ one node in the tree · routed by capability
INTERACTIVE · 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 corrects

HTML 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.

HTML answers "What should the browser paint now?"
Flight answers "What React tree, props, and module references describe this UI?"
Client JS answers "What parts need to become interactive?"

Four myths this corrects

Each card is a tidy mental model that quietly breaks. Tap one for the correction.

the whole pipeline, one breath One request starts a server render. Server Components execute and produce a serializable result — the Flight payload — including normal output, serialized props, suspended regions, and client boundaries. React DOM Server uses the renderable result to produce the HTML stream, which can include Server Component markup and initial Client Component markup. Client Components also ship JS, so React hydrates them and attaches listeners. HTML says what to paint; Flight says what tree describes it; client JS makes the boundaries live.
Back to JIT-Atlas