Bundle benchmark
Measured JavaScript/WASM for a counter page (SSR heading + increment button) across Resuma, Qwik, Leptos, Astro, Next.js, SvelteKit, SolidStart, React (Vite), and templ + HTMX. Static Resuma pages ship zero client JS.
Summary (gzip)
| Framework | Initial load | First interaction | Static page |
|---|---|---|---|
| Resuma | 901 B | 4.20 KiB | 0 B |
| Leptos 0.7 | 79.02 KiB | 79.02 KiB | — |
| Next.js 16 (App Router) | 142.43 KiB | 142.43 KiB | — |
| React 19 (Vite SPA) | 57.99 KiB | 57.99 KiB | — |
| Astro 5.7 (React island) | 57.76 KiB | 57.76 KiB | — |
| SvelteKit 2.57 | 27.71 KiB | 27.71 KiB | — |
| Qwik 1.20 | 1.96 KiB | 22.32 KiB | — |
| SolidStart 1.2 | 16.75 KiB | 16.75 KiB | — |
| templ + HTMX 2 | 16.21 KiB | 16.21 KiB | — |
Methodology
- Same UX: SSR heading + one interactive counter button.
- Production builds in benchmark/*-counter plus runtime/ for Resuma.
- Report minified raw + gzip + brotli from build artifacts (simulated compression in run.mjs).
- Initial load = JS required before the page can resume/hydrate interactivity.
- First interaction = total JS transferred when the user clicks + (includes lazy chunks where applicable).
- Regenerate anytime: node benchmark/run.mjs -> benchmark/results.json.
Why initial = first click for most rows
Classic hydration (SvelteKit, SolidStart, Astro client:load, React SPA, Leptos, Next counter) ships all client JS referenced by the HTML on first paint. There is no lazy split on click, so both columns show the same total. Only Resuma and Qwik defer meaningful runtime work until first interaction.
Scaffold caveat
Our Next counter uses the default create-next-app scaffold (App Router, Tailwind, Geist fonts, Turbopack chunks). That is an honest out-of-the-box number, not a lean production baseline. Optimized App Router setups often report 67-78 KiB first-load JS when Server Components eliminate most client bundles. Treat 142 KiB as upper-bound for default tooling, not a tuned app.
External validation
Independent published measurements agree with our numbers (same ranking, same order of magnitude). Qwik preloader ~2 KiB, HTMX ~16 KiB, Astro React client ~59 KiB, React 19 Vite ~59 KiB, SvelteKit ~32 KiB in the SendOT portfolio series - all line up with our counter benchmark.
Full reference table with links in the repo README. Next.js 142 KiB reflects the default create-next-app scaffold; optimized App Router apps often land near 67-78 KiB first-load JS. benchmark/README.md
What each framework ships
| Framework | Initial load | First interaction | Notes |
|---|---|---|---|
| Resuma | loader.js | loader.js + core.js | Rust SSR + resumability; static pages = 0 B |
| Leptos | .wasm + glue | same | Rust SSR + WASM hydrate |
| Next.js | firstLoadChunkPaths (App Router) | same | React SSR + hydration; default create-next-app scaffold |
| React (Vite) | single SPA bundle | same | Client-rendered baseline |
| Astro | React island + client runtime | same | client:load React island |
| SvelteKit | entry + app + runtime chunks | same | SSR + client hydration (adapter-static) |
| Qwik | preloader | preloader + core + route + onClick chunk | Resumability (JS) |
| SolidStart | client + web + index chunks | same (full hydration on load) | Solid SSR + hydration |
| templ + HTMX | htmx.min.js | same (server round-trip on click) | Go SSR + HTMX; no client app bundle |
Resuma (split runtime)
| Bundle | When loaded | Raw | Gzip | Brotli |
|---|---|---|---|---|
loader.js | Interactive pages only | 1.82 KiB | 901 B | 746 B |
core.js | First interaction | 8.68 KiB | 3.32 KiB | 2.93 KiB |
Static docs page | Never | 0 B | 0 B | 0 B |
Reproduce locally
node benchmark/run.mjs
# Or individually:
cd runtime && npm run build && npm run size
cd benchmark/qwik-counter && npm run build
cd benchmark/leptos-counter && wasm-pack build --target web --release
cd benchmark/astro-counter && npm run build
cd benchmark/next-counter && npm run build
cd benchmark/sveltekit-counter && npm run build
cd benchmark/solidstart-counter && npm run build
cd benchmark/react-counter && npm run build
curl -H "Accept-Encoding: gzip" http://127.0.0.1:3000/_resuma/benchmark.json
cargo run -p example-counterTakeaways
- Resuma: ~901 B gzip initial, ~4 KiB gzip to full interactivity - no WASM, lazy core on first click.
- Qwik: smallest resumable JS preloader (~2 KiB gzip), core chunk adds ~20 KiB gzip on first click.
- templ + HTMX: ~16 KiB gzip for HTMX alone; interactivity is a server round-trip, not client hydration.
- SolidStart / SvelteKit: mid-tier hydration bundles (~17-28 KiB gzip) for a minimal counter.
- Astro / React: ~58 KiB gzip - React runtime dominates whether island or SPA.
- Leptos: WASM hydration bundle ~79 KiB gzip even for a minimal counter.
- Next.js: ~142 KiB gzip first-load JS on default App Router scaffold (includes framework + React runtime).
- Static-first: only Resuma skips all client JS on non-interactive pages.