Renderer Benchmarks

Three storefront pages are rendered three ways with the same props. Every run asserts the output is byte-identical before it reports a number, so every renderer produced the same HTML.

Warm render

PageReact in QuickJSReact in V8FSRFSR + mimallocvs QuickJS
Catalog (12 cards)1.86 ms132 µs128 µs82 µs22.6x
Product page369 µs18.2 µs18.0 µs12.5 µs29.4x
Cart (3 items)426 µs11.5 µs19.5 µs12.7 µs33.5x

FSR has two columns because the allocator changes its speed by about 1.5x and you choose the allocator when you deploy. Neither the library nor the host picks one. QuickJS uses the same allocator as FSR. Its numbers barely change with the allocator, because most of its allocation happens inside the engine.

How it scales

This renders the same catalogue route at four list lengths, against React in V8 on the same props, with the output checked byte-identical at every length.

ProductsReact in V8FSRLead
120.0 µs15.1 µs1.32x
12135 µs82.2 µs1.64x
1001.05 ms615 µs1.71x
100010.9 ms6.21 ms1.76x

Both scale linearly across three orders of magnitude and FSR’s lead grows with list size, so the lead does not depend on one particular page. Each product costs 6.15 µs in FSR and 10.51 µs in V8. The single-item row is mostly fixed per-page overhead, which matters less as the list grows.

Under load

Several requesters render the catalogue as fast as they can for ten seconds and count the completed renders. React server rendering is synchronous work an event loop cannot interleave, so Node is shown both ways it can use more than one core: isolates inside one process and a process per requester.

Renders per second, higher is better:

RequestersQuickJSV8, worker threadsV8, process eachFSR
15387,4427,48112,033
286711,35714,24323,436
41,73411,70827,89540,694
83,3055,54954,35374,606

What each request waits while the others compete, lower is better:

RequestersQuickJSV8, worker threadsV8, process eachFSR
11.86 ms134 µs134 µs83 µs
82.42 ms1.44 ms147 µs107 µs

Worker-thread throughput peaks at four requesters and then drops, so compare FSR against the process column, where FSR is 1.37x faster. Node processes take more resources overall, with independent garbage collectors.

Cold start

Bringing up an engineCatalogProductCart
QuickJS context, warm process29.3 ms29.4 ms28.4 ms
Node process and module graph68.5 ms68.4 ms57.9 ms

The two rows measure different things, so don’t compare them with each other. FSR has neither cost. There is no JavaScript engine on the server and a render starts from a tree the build already compiled.

How to read this

These pages are markup-heavy. They loop over data and emit tags, which is most of what a server renderer actually does. It is also where an interpreter walking a lowered tree does less work than a JIT that must first build JavaScript objects to walk. A JIT wins on a component doing real arithmetic and no such page is measured here.

Apple M4 Pro, high power mode. Criterion for the Rust and QuickJS rows; the V8 rows are Node v26.8.1, 2000 samples after 500 warmups. Measured 2026-09-07.