Skip to content

[pull] main from facebook:main#521

Merged
pull[bot] merged 1 commit intocode:mainfrom
facebook:main
Apr 2, 2026
Merged

[pull] main from facebook:main#521
pull[bot] merged 1 commit intocode:mainfrom
facebook:main

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented Apr 2, 2026

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

This PR adds a benchmark fixture for measuring the performance overhead
of the React Server Components (RSC) Flight rendering compared to plain
Fizz server-side rendering.

### Motivation

Performance discussions around RSC (e.g. #36143, #35125) have
highlighted the need for reproducible benchmarks that accurately measure
the cost that Flight adds on top of Fizz. This fixture provides multiple
benchmark modes that can be used to track performance improvements
across commits, compare Node vs Edge (web streams) overhead, and
identify bottlenecks in Flight serialization and deserialization.

### What it measures

The benchmark renders a dashboard app with ~25 components (16 client
components), 200 product rows with nested data (~325KB Flight payload),
and ~250 Suspense boundaries in the async variant. It compares 8 render
variants: Fizz-only and Flight+Fizz, across Node and Edge stream APIs,
with both synchronous and asynchronous apps.

### Benchmark modes

- **`yarn bench`** runs a sequential in-process benchmark with realistic
Flight script injection (tee + `TransformStream`/`Transform` buffered
injection), matching what real frameworks do when inlining the RSC
payload into the HTML response for hydration.
- **`yarn bench:bare`** runs the same benchmark without script
injection, isolating the React-internal rendering cost. This is best for
tracking changes to Flight serialization or Fizz rendering.
- **`yarn bench:server`** starts an HTTP server and uses `autocannon` to
measure real req/s at `c=1` and `c=10`. The `c=1` results provide a
clean signal for tracking React-internal changes, while `c=10` reflects
throughput under concurrent load.
- **`yarn bench:concurrent`** runs an in-process concurrent benchmark
with 50 in-flight renders via `Promise.all`, measuring throughput
without HTTP overhead.
- **`yarn bench:profile`** collects CPU profiles via the V8 inspector
and reports the top functions by self-time along with GC pause data.
- **`yarn start`** starts the HTTP server for manual browser testing.
Appending `.rsc` to any Flight URL serves the raw Flight payload.

### Key findings during development

On Node 22, the Flight+Fizz overhead compared to Fizz-only rendering is
roughly:

- **Without script injection** (`bench:bare`): ~2.2x for sync, ~1.3x for
async
- **With script injection** (`bench:server`, c=1): ~2.9x for sync, ~1.8x
for async
- **Edge vs Node** adds another ~30% for sync and ~10% for async, driven
by the stream plumbing for script injection (tee + `TransformStream`
buffering)

The async variant better represents real-world applications where server
components fetch data asynchronously. Its lower overhead reflects the
fact that Flight serialization and Fizz rendering can overlap with I/O
wait times, making the added Flight cost a smaller fraction of total
request time.

The benchmark also revealed that the Edge vs Node gap is negligible for
Fizz-only rendering (~1-2%) but grows to ~15% for Flight+Fizz sync even
without script injection. With script injection (tee + `TransformStream`
buffering), the gap roughly doubles to ~30% for sync. The async variants
show smaller gaps (~5% without, ~10% with injection).
@pull pull Bot locked and limited conversation to collaborators Apr 2, 2026
@pull pull Bot added the ⤵️ pull label Apr 2, 2026
@pull pull Bot merged commit 1b45e24 into code:main Apr 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant