| 1 | # Flight SSR Benchmark |
| 2 | |
| 3 | Measures the performance overhead of the React Server Components (RSC) Flight pipeline compared to plain Fizz server-side rendering, across both Node and Edge (web streams) APIs. |
| 4 | |
| 5 | ## Prerequisites |
| 6 | |
| 7 | Build React from the repo root first: |
| 8 | |
| 9 | ```sh |
| 10 | yarn build-for-flight-prod |
| 11 | ``` |
| 12 | |
| 13 | Then install the fixture's dependencies: |
| 14 | |
| 15 | ```sh |
| 16 | cd fixtures/flight-ssr-bench |
| 17 | yarn install |
| 18 | ``` |
| 19 | |
| 20 | ## Scripts |
| 21 | |
| 22 | | Script | Purpose | |
| 23 | | --- | --- | |
| 24 | | `yarn bench` | Sequential benchmark with Flight script injection (realistic framework pipeline). Best for measuring Edge vs Node overhead. | |
| 25 | | `yarn bench:bare` | Sequential benchmark without script injection. Best for measuring React-internal changes (e.g. Flight serialization optimizations) with less noise from stream plumbing. | |
| 26 | | `yarn bench:server` | HTTP server benchmark using autocannon at c=1 and c=10. Best for measuring real-world req/s. The c=1 results are also useful for tracking React-internal changes. | |
| 27 | | `yarn bench:concurrent` | In-process concurrent benchmark (50 in-flight renders). Measures throughput under load without HTTP overhead. | |
| 28 | | `yarn bench:profile` | CPU profiling via V8 inspector. Saves `.cpuprofile` files to `build/profiles/`. | |
| 29 | | `yarn start` | Starts the HTTP server for manual browser testing at `http://localhost:3001`. Append `.rsc` to any Flight URL to see the raw Flight payload. | |
| 30 | |
| 31 | ## What it measures |
| 32 | |
| 33 | Each script benchmarks 8 render variants: |
| 34 | |
| 35 | - **Fizz (Node, sync/async)** -- plain `renderToPipeableStream`, no RSC |
| 36 | - **Fizz (Edge, sync/async)** -- plain `renderToReadableStream`, no RSC |
| 37 | - **Flight + Fizz (Node, sync/async)** -- full RSC pipeline: Flight server (`renderToPipeableStream`) -> Flight client (`createFromNodeStream`) -> Fizz (`renderToPipeableStream`) |
| 38 | - **Flight + Fizz (Edge, sync/async)** -- full RSC pipeline: Flight server (`renderToReadableStream`) -> Flight client (`createFromReadableStream`) -> Fizz (`renderToReadableStream`) |
| 39 | |
| 40 | The "sync" variants use a fully synchronous app (no Suspense boundaries). The "async" variants use per-row async components with staggered delays and individual Suspense boundaries (~250 boundaries per render). |
| 41 | |
| 42 | ### Script injection |
| 43 | |
| 44 | The `yarn bench` and `yarn bench:server` scripts simulate what real frameworks do: tee the Flight stream and inject `<script>` hydration tags into the HTML output. This uses a `setTimeout(0)`-buffered Transform/TransformStream to avoid splitting mid-HTML-tag. `yarn bench:bare` skips this for cleaner React-internal measurement. |
| 45 | |
| 46 | ## Test app |
| 47 | |
| 48 | A dashboard with ~25 components (16 client components), rendering: |
| 49 | |
| 50 | - 200 product rows with nested reviews, specifications, and supplier data (~325KB Flight payload) |
| 51 | - 50 activity feed items |
| 52 | - Stats grid with 24-month chart data |
| 53 | - Sidebar with navigation and recent activity |
| 54 | |
| 55 | ## Output |
| 56 | |
| 57 | Each variant reports render latency stats, GC pauses, and (when run with `--expose-gc`, which the `yarn bench*` scripts do) the heap retained after the run settles. |
| 58 | |
| 59 | The overhead tables show two comparisons: |
| 60 | |
| 61 | 1. **Flight overhead** -- Flight+Fizz vs Fizz-only (how much RSC adds) |
| 62 | 2. **Edge vs Node** -- web streams vs Node streams (stream implementation cost) |
| 63 | |
| 64 | Delta is shown as percentage change plus a factor (e.g. `+120% 2.20x` means 2.2x slower). |