Benchmarks

One storefront, written in six frameworks and served on the same machine. On the product page FSR answers 77x as many requests as Next.js and beats Leptos, which is Rust from end to end.

77xas many requests a second as Next.js
8.4xas many requests a second as SvelteKit
1.2xas many requests a second as Leptos, the next fastest
0.56 ms99th percentile response time with 32 clients at once
Product page, requests a second with 32 clients at once. Click a row for the exact count.
SnapFire FSR116k116,484
Leptos94k93,882
Dioxus32k32,289
SvelteKit14k13,800
Nuxt3.9k3,893
Next.js1.5k1,514

Where FSR doesn’t win

  • A loader that does heavy arithmetic in TypeScript runs slower than SvelteKit on V8. Move the loop to Rust with #[native] and FSR is fastest on two of the three compute pages.
  • A cold production build takes 45 s. A warm one takes 0.8 s, the fastest of the six.
  • Under load it holds more memory than Leptos and Dioxus, though less than the three Node servers.

The storefront arena

Every app serves the same 48 products, the same stylesheet and the same markup. Each page is checked for its status, its text and its product count before it is loaded. The figures are medians across repetitions, and green marks the best figure in a row and every figure within 5% of it. The tables here use 32 concurrent clients. Click a cell for the exact count. The full results have every client count from 1 to 256, CPU and memory for each page, every page’s size and the method.

SnapFire FSR 0.23.0, Next.js 16.4.4, SvelteKit 3.1.1, Nuxt 4.6.4, Leptos 0.8.23 and Dioxus 0.7.10. Apple M4 Pro, high power mode, measured 2026-11-03 to 2026-11-07.

Requests per second

Responses read in full per second with 32 concurrent clients, higher is better.

SnapFire FSRNext.jsSvelteKitNuxtLeptosDioxus
home
The landing page: a hero and three features, no data.
175k175,288
8.5k8,541
29k28,770
30k30,133
156k155,975
36k36,077
counter
A fixed page holding one component that hydrates in the browser.
175k175,050
8.6k8,644
31k31,196
30k29,812
158k157,534
36k36,088
catalog
All 48 products, sorted by the query.
55k55,021
881
6.5k6,487
2k1,999
35k35,330
13k13,006
product
One product by its slug, with four related products.
116k116,484
1.5k1,514
14k13,800
3.9k3,893
94k93,882
32k32,289
search
Products whose name or tag matches the query.
102k102,263
1.5k1,472
12k11,520
3.3k3,344
78k78,166
27k26,985
missing
A slug with no product, answered 404.
136k135,717
1.3k1,280
16k16,113
2.4k2,420
105k105,319
35k34,701
api
The catalog as JSON, with no render.
163k162,645
6.7k6,712
25k25,338
42k42,368
173k172,697
150k149,825
table
500 rows from simple data, so the render is the cost.
15k14,936
181
1.1k1,132
1.2k1,243
14k14,217
11k10,834

Latency

99th percentile in milliseconds, from sending a request to reading its body, with 32 concurrent clients. Lower is better.

SnapFire FSRNext.jsSvelteKitNuxtLeptosDioxus
home0.287.312.191.520.352.45
counter0.287.221.441.550.352.46
catalog1.2544.747.9635.231.834.47
product0.5627.783.9516.770.612.51
search0.6628.384.6719.920.982.74
missing0.4333.483.4819.890.472.51
api0.358.662.811.560.290.37
table4.23217.6351.6453.124.457.64

The api row serves the catalog as JSON with no render, which is where Leptos and Dioxus catch up.

Compute pages

Requests per second with 32 concurrent clients, higher is better.

SnapFire FSRFSR, native loaderNext.jsSvelteKitNuxtLeptosDioxus
primes
A sieve of Eratosthenes up to 50,000: tight numeric loops over an array.
1.8k1,848
79k79,217
1.1k1,062
2.6k2,564
1.7k1,742
73k73,409
32k31,566
report
5,000 generated orders totalled per product and sorted by revenue.
6.4k6,381
104k104,390
1.6k1,562
10k10,147
3.8k3,789
88k88,265
33k32,928
words
Word counts over a built-up text: splitting, lowercasing and counting.
1.4k1,383
5.4k5,400
450594551
5.9k5,900
5.2k5,180

Each compute loader loops and allocates on every request: a sieve of Eratosthenes, an aggregation of generated orders sorted with a comparator and a word count over a built-up text. FSR runs the TypeScript loader without a JavaScript engine. On these pages that is slower than SvelteKit on V8 for primes and report. The native column is the same page with its loader calling the Rust that Leptos and Dioxus run, through #[native]. The view and the render are FSR’s either way.

Starting and memory

Lower is better.

SnapFire FSRNext.jsSvelteKitNuxtLeptosDioxus
Spawn to first 20021 ms322 ms54 ms212 ms30 ms24 ms
Resident at first 20018.9 MB95.0 MB60.4 MB80.1 MB5.7 MB10.2 MB
Peak resident over the run388.8 MB669.6 MB406.1 MB791.9 MB101.0 MB97.8 MB

FSR starts fastest. Under load it holds more memory than Leptos and Dioxus, though less than the three Node servers.

Production build

Wall time, lower is better. A warm build follows a change to one page.

SnapFire FSRNext.jsSvelteKitNuxtLeptosDioxus
cold45.3 s2.8 s1.5 s3.4 s48.5 s56.9 s
warm0.8 s1.4 s1.3 s2.6 s14.0 s13.2 s

Size

Lower is better. Page sizes are for the product page, gzipped.

SnapFire FSRNext.jsSvelteKitNuxtLeptosDioxus
Total to deploy25.7 MiB151.8 MiB108.8 MiB109.7 MiB13.3 MiB8.6 MiB
Server binary, stripped16.9 MiB---7.1 MiB4.7 MiB
Document1.9 KiB2.1 KiB1.3 KiB1.3 KiB1.6 KiB1.7 KiB
JavaScript40.0 KiB134.7 KiB34.2 KiB74.6 KiB6.7 KiB15.8 KiB
WebAssembly----257.1 KiB483.2 KiB
Requests to show it32, then 7 later812944

The total to deploy includes Node for Next.js, SvelteKit and Nuxt, since it has to be installed beside them. Leptos and Dioxus hydrate by loading the whole app as WebAssembly. The other four hydrate with JavaScript alone.

The load client runs on the same machine as the servers and takes CPU from them. FSR, Leptos and Dioxus serve on every core. Next.js, SvelteKit and Nuxt each serve from one Node process. Running several behind a balancer is a deployment choice the arena does not make.

The renderer alone

Three storefront pages are rendered three ways with the same props, with no HTTP in the way. 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 code doing real arithmetic, which the arena’s compute pages above measure.

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.