Renderer Benchmarks

The same three storefront pages, the same props, rendered three ways. Every run asserts the output is byte-identical before it reports a number, so the comparison is between renderers and not between outputs.

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

Two FSR columns because the allocator is a deployment choice worth about 1.5x and nothing in the library or the host picks one for you. QuickJS is quoted with the same allocator as FSR; it barely moves either way, because the engine’s own allocation dominates.

How it scales

The same catalogue route rendered 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 are linear across three orders of magnitude and the lead widens with size, so it is a property of the design rather than of one page. Per product the cost settles at 6.15 µs for FSR against 10.51 µs for V8. The single-item row is fixed page overhead that amortises away.

Under load

Several requesters rendering the catalogue flat out for ten seconds each, counting what came out. 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 threads peak at four and then go backwards, so the process column is the fair comparison, where FSR leads by 1.37x. 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

These two are different units and neither answers the other. FSR pays neither: there is no engine in the serving path, so a render starts on a tree the build already lowered.

How to read this

These pages are markup-heavy. They loop over data and emit tags, which is the work a server renderer actually does most of and it is where an interpreter walking a lowered tree does less than a JIT that must first build JavaScript objects to walk. A component doing real arithmetic is where a JIT wins instead 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.

Built with SnapFire FSR. Pure Rust runtime, zero Node.js on the server.

Proudly Created by Excerion Sun LLC