Documentation
Benchmark
Two studies: what the server does on 0.2.0, and what a visitor's browser waits for. One named run each, with what varies between runs called out.
Two studies, measuring different halves of the same question. The first is what the server does — throughput, latency, memory and build, on Stoneware 0.2.0. The second is what a visitor's browser experiences, measured earlier on 0.1.3 under Lighthouse throttling. Neither replaces the other, and both are reproducible rather than asserted.
Study one: what the server does
Twenty articles and an index — 42,871 words — built three times from a byte-identical content.json, through a byte-identical stylesheet, into byte-identical markup. Verified: all 21 routes render the same tags and the same text in all three apps, differing only in how each escapes an apostrophe. Measured over HTTP against each framework's own production server.
Every number below comes from one run, named in the file it was taken from. The size figures are deterministic — the same bytes every time — and the timing figures are not. Where a number moves between runs, this page says so rather than averaging the movement away.
an article page — no interactivity, 19 of the 21 routes
HTML gzip JS requests
────────────────────────────────────────────────────
Stoneware 15.4 KB 2.8 KB 0 B 2
Astro 15.2 KB 2.8 KB 0 B 2
Next.js 39.9 KB 5.3 KB 576 KB 8Stoneware and Astro are within a rounding error of each other, because both send a document and nothing else. Next.js sends 576 KB of JavaScript to a page with no interactive element on it at all — and its HTML is two and a half times larger, because 23.4 KB of the document is the article re-encoded as an inline RSC payload. That is 59% of the response, and it is the content going out a second time.
HTML gzip JS JS gzip requests ────────────────────────────────────────────────────── Stoneware 3.2 KB 11.3 KB 4.8 KB 4 Astro 3.3 KB 0.4 KB 0.3 KB 2 Next.js 5.8 KB 577 KB 173.7 KB 9 Stoneware an island, hydrated, state in signals Astro a plain <script> tag — no framework at all Next.js a "use client" component
Astro wins this row and the report says so. A script tag beats a hydrated island for one text box, and 0.3 KB against 4.8 KB is not close. Stoneware's 4.8 KB is signals-core plus the hydration runtime, which is the price of the island model rather than an inefficiency in it — but it is a price, and Astro does not pay it here.
HTML HTML gzip JS gzip pages with no JS ─────────────────────────────────────────────────────────────── Stoneware 313.1 KB 60.3 KB 4.8 KB 20 of 21 Astro 309.9 KB 59.4 KB 0.3 KB 20 of 21 Next.js 806.0 KB 112.7 KB 255.3 KB 0 of 21
Build, and what it leaves on disk
build peak RAM output deployable node_modules ────────────────────────────────────────────────────────────────────── Stoneware 0.49s 88 MB 0.68 MB 0.35 MB 6 MB Astro 5.69s 356 MB 0.31 MB 0.31 MB 136 MB Next.js 16.64s 1143 MB 32.18 MB 6.46 MB 396 MB
Deployable excludes build scratch that never ships — for Next that is cache, trace, types and turbopack inside .next, which is the difference between 32 MB and 6.5 MB. Quoting the larger number would be misleading, so both are here.
The build comparison is not like-for-like and never was. stoneware build emits a server bundle; Astro and Next prerender 21 HTML files. The comparable command is stoneware export. What the column does show honestly is peak memory: 88 MB against 356 and 1143, for the same twenty articles.
Latency
p50 p95 p99 max ───────────────────────────────────────────────── Stoneware 1.13ms 3.58ms 4.46ms 4.55ms Astro 1.74ms 2.23ms 3.51ms 3.68ms Next.js 1.84ms 2.51ms 3.80ms 4.26ms
Stoneware has the lowest median and, in this run, the highest p95, p99 and max. That shape — a fast middle and a long tail — is what a JIT and a generational collector look like from the outside, and it is inherited rather than introduced: a bare Bun.serve returning a fixed string has roughly 2.2x the p99 of a bare node:http server doing the same thing. Astro and Next run on Node.
The tail is the least reproducible number on this page. Repeated runs moved Stoneware's p99 by a factor of four while its median barely shifted, and Astro's tail moved too. Rank the medians if you must rank something; do not rank the tails on one run, including this one.
Throughput
connections 1 10 25 50 100 250 ────────────────────────────────────────────────────────── Stoneware 1443 3047 2483 2060 2236 3355 Astro 648 1554 1890 1922 1984 2186 Next.js 579 928 827 957 920 879
Read the separation, not the curve. Stoneware is roughly a third above Astro and three to four times Next across the range, and that ordering held in every run. The individual points do not form a clean line — Stoneware's row goes up, down, and up again — because past a few hundred requests per second the load generator is the thing being measured.
These are floors, not ceilings. The load generator runs on the same machine as the server, so both compete for the same twelve cores. A number here is a statement about this harness on this box, not about capacity.
Stoneware 166 MB Astro 352 MB a static file server, not a renderer Next.js 398 MB
Astro's row is measuring a different job. astro preview serves prebuilt files; Stoneware and Next render on each request. It is the right number for that deployment shape and it is not the same work.
How these numbers were taken
- One run: results/history/2026-08-19T18-24-23.json in the benchmark repository. Sizes are identical in every run; timings are not, and the tail least of all.
- A run is only usable if the machine was quiet. Astro and Next are the control group — their code has not changed — so when their build times move together, the run is measuring the machine. One run was discarded on that basis, with Astro building in 50s against its usual 6s.
- Tailwind and next/font/google were removed from the Next scaffold, so CSS and fonts are constants rather than a second variable.
- The Next app uses plain <a> rather than next/link, which is the lighter of the two options. That makes Next's numbers better than an idiomatic Next app's would be.
- JavaScript counts inline scripts and follows module imports. Next delivers its RSC payload inline and Stoneware's island chunk imports a shared runtime; counting only script tags would under-report both.
- Time to first byte is loopback. Real first-byte time is this plus the round trip to wherever the app is hosted.
Host: 12-core AMD Ryzen 5 5500U, Bun 1.3.14. The harness, the corpus generator and the operating manual are in the benchmark repository, and every figure here can be regenerated from it.
Study two: what the browser experiences
An earlier study, on Stoneware 0.1.3, measuring the other half — what a visitor on a throttled connection actually waits for. The framework has moved on since; the client-side model it measures has not, because a page with no islands still ships nothing.
A portfolio and blog: home, about, contact, a blog index with five posts, and a docs section with seven pages. Built three times with matching content and the same five interactive components, then measured under Lighthouse mobile throttling — 1638 Kbps, 150 ms RTT, 4x CPU slowdown — 10 runs per page, median reported.
Stoneware Astro Next.js ────────────────────────────────────────────────────── JS transferred 14.2 KB 193.1 KB 346.0 KB HTML 3.4 KB 8.1 KB 10.2 KB Total transferred 22.5 KB 205.2 KB 359.4 KB Requests 8 8 7 ────────────────────────────────────────────────────── LCP 1217 ms 2253 ms 2965 ms FCP 1062 ms 1429 ms 754 ms Total blocking time 0 ms 0 ms 58 ms CLS 0.000 0.000 0.000 Lighthouse perf 100 99 95 ────────────────────────────────────────────────────── Build, 16 pages cold 0.71 s 35.6 s 61.6 s
JavaScript is the whole story
All three score CLS 0.000 and a TTFB of 1–3 ms against a warm local server, so layout stability and server latency are noise here. What separates them is the client bundle: 14.2 KB against 193.1 and 346.0, uncompressed, for the same five islands. At 1638 Kbps that is roughly one and 1.7 extra seconds of download, and the LCP spread tracks it almost exactly.
Stoneware ██ 14.2 KB 1.0x Astro ████████████████████████ 193.1 KB 13.6x Next.js ███████████████████████████████████ 346.0 KB 24.4x
The shape of the result is more interesting than the totals
Astro's LCP is almost perfectly flat — 2252 to 2253 ms across nearly every page, whether that page carries 426 or 3,367 bytes of content. Next.js shows the same pattern at about 2960 ms. A fixed cost is dominating: the React client runtime sits on the critical path every time, so page weight is irrelevant beside it.
Stoneware is the only one whose LCP actually varies with the page, from 909 ms to 1512 ms. That is what it looks like when there is no fixed runtime cost for content to hide behind — the page is the only thing being paid for.
Framework overhead also scales differently. Stoneware adds a roughly constant ~2.4 KB of HTML per page. Next.js grows with content — 9.7 KB on the home page to 12.2 KB on a blog post — because the RSC flight payload re-encodes the rendered output alongside the HTML, so the content is effectively sent twice.
Where the others win
- Next.js wins FCP outright: 754 ms against 1062 and 1429. It inlines more of the critical path and manages CSS through the bundle, so first paint lands early — and then LCP waits about 2.2 s longer for the JavaScript that makes the paint useful. Fast first paint, slow useful paint.
- Next.js also serves the fewest requests, 7 against 8.
- Astro matches Stoneware on total blocking time at 0 ms. Next.js is the only one with meaningful main-thread blocking, at 58 ms median and up to 82 ms.
The Lighthouse scores are 100, 99 and 95. All three would pass a casual audit, which is worth knowing about the score itself: a 24x spread in JavaScript shipped is invisible inside it.
Reading the numbers fairly
- Bytes are uncompressed. Production would gzip or brotli all three, which narrows the transfer gap — but not the parse-and-execute gap. 346 KB of JavaScript still costs main-thread time that 14.2 KB does not.
- The build column is not like-for-like. stoneware build emits a server bundle; Astro and Next.js prerender 16 HTML files. The comparable command is stoneware export, measured at 0.63 s. Build timing was also the noisiest metric — treat the ordering as the result and the absolute values as indicative.
- Serving modes differ. Stoneware rendered each request through stoneware start; the other two were prerendered static files. That favours them on TTFB, though all three measured 1–3 ms locally.
- The content is lighter than a real site's — posts run 214 to 500 words. A heavier corpus would widen the HTML gaps and shift LCP further toward content download.
Measured on one machine, in one session, with the browser open. A 10-run median absorbs some of that but not all of it. The numbers are reproducible from the benchmark repository rather than asserted here.
Something wrong in the framework itself rather than the page? Open an issue on GitHub.