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)

FrameworkInitial loadFirst interactionStatic page
Resuma901 B4.20 KiB0 B
Leptos 0.779.02 KiB79.02 KiB
Next.js 16 (App Router)142.43 KiB142.43 KiB
React 19 (Vite SPA)57.99 KiB57.99 KiB
Astro 5.7 (React island)57.76 KiB57.76 KiB
SvelteKit 2.5727.71 KiB27.71 KiB
Qwik 1.201.96 KiB22.32 KiB
SolidStart 1.216.75 KiB16.75 KiB
templ + HTMX 216.21 KiB16.21 KiB

Methodology

  1. Same UX: SSR heading + one interactive counter button.
  2. Production builds in benchmark/*-counter plus runtime/ for Resuma.
  3. Report minified raw + gzip + brotli from build artifacts (simulated compression in run.mjs).
  4. Initial load = JS required before the page can resume/hydrate interactivity.
  5. First interaction = total JS transferred when the user clicks + (includes lazy chunks where applicable).
  6. 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

FrameworkInitial loadFirst interactionNotes
Resumaloader.jsloader.js + core.jsRust SSR + resumability; static pages = 0 B
Leptos.wasm + gluesameRust SSR + WASM hydrate
Next.jsfirstLoadChunkPaths (App Router)sameReact SSR + hydration; default create-next-app scaffold
React (Vite)single SPA bundlesameClient-rendered baseline
AstroReact island + client runtimesameclient:load React island
SvelteKitentry + app + runtime chunkssameSSR + client hydration (adapter-static)
Qwikpreloaderpreloader + core + route + onClick chunkResumability (JS)
SolidStartclient + web + index chunkssame (full hydration on load)Solid SSR + hydration
templ + HTMXhtmx.min.jssame (server round-trip on click)Go SSR + HTMX; no client app bundle

Resuma (split runtime)

BundleWhen loadedRawGzipBrotli
loader.jsInteractive pages only1.82 KiB901 B746 B
core.jsFirst interaction8.68 KiB3.32 KiB2.93 KiB
Static docs pageNever0 B0 B0 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-counter

Takeaways

  • 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.