Most pages are documents, not applications
The framing that got lost somewhere in the last decade is that a web page and a web application are different things. A documentation site is a document. A product page is a document with a button on it. A blog post is a document, full stop. None of them stop being documents just because the tooling that produced them was built for applications.
What the application-shaped default costs you is not abstract. The browser downloads a runtime, parses it, executes it, and then re-creates in memory a DOM the server already sent as text. On a fast laptop on a fast connection this is invisible. On a mid-range phone on a congested network - which is the median condition for most of the world - it is the difference between a page that is readable immediately and one that is readable in three seconds.
Stoneware inverts the default rather than adding an escape hatch to it. You do not opt out of shipping JavaScript; you opt in, per component, and you can see exactly which components did.
What Stoneware actually is
It's a Bun-native framework - not Bun-compatible, actually built on Bun's own APIs. Routing, bundling, escaping and CSRF all come from the runtime rather than from a stack of dependencies layered on top of it.
Every route renders to complete HTML on the server. If a page has no interactive components, it ships zero bytes of JavaScript. If a component does need interactivity - a cart button, a theme switcher, a search box - it becomes an island, and only that island's code reaches the browser.
- ✓Bun.serve for the HTTP layer, so there is no separate server framework to configure.
- ✓Bun.build for bundling island code - one bundler, already in the runtime.
- ✓Bun.escapeHTML for auto-escaping every interpolated value by default.
- ✓Bun.CSRF for token generation and verification on state-changing requests.
- ✓Bun.FileSystemRouter for file-based routes without a routing dependency.
The numbers, not just the pitch
A framework author claiming their framework is fast is worth nothing on its own, so I built the same 16-page site three times - matching content, matching five interactive components - once in Stoneware, once in Astro, once in Next.js, and measured all three the same way.
| Metric | Stoneware | Astro 5.18 | Next.js 15.5 |
|---|---|---|---|
| JS transferred | 14.2 KB | 193.1 KB | 346.0 KB |
| LCP | 1217 ms | 2253 ms | 2965 ms |
| Lighthouse performance | 100 | 99 | 95 |
| Cold build, 16 pages | 0.71 s | 35.6 s | 61.6 s |
What the Lighthouse score hides
That is 13.6x less JavaScript than Astro and 24.4x less than Next.js, for the exact same interactive functionality. The build-time gap is the one that changes how the work feels day to day - a cold build in under a second instead of a minute means you stop context-switching while you wait.
But look at the Lighthouse row again: 100, 99, 95. Those scores look close enough to ignore, and they hide the 24x spread completely. A perfect score does not mean nothing was shipped. It means what shipped did not block paint. Lighthouse is measuring whether the JavaScript got out of the way in time, not whether it needed to be there - so if payload size is what you care about, measure payload size.
The same caveat runs in the other direction, and I would rather say it than have someone else say it for me: a synthetic benchmark on one site shape is evidence about that site shape. Build your own version of the page you actually ship and measure that.
Secure by default, not by remembering to configure it
Auto-escaping, CSRF verification and a restrictive Content-Security-Policy are on before you write a line of config. The unsafe path - raw(), dangerouslySetInnerHTML - is there when you need it, and it is named to be easy to grep for and easy to review.
The idea is simple: you shouldn't have to remember every security primitive before deploying a basic site. Defaults are the only security control that protects people who never read the docs, and most people never read the docs.
Practice this now
Actively hardened, not just shipped and abandoned
Two recent patch releases focused entirely on production reliability.
v0.1.8 fixed a deployment bug where Vercel apps would render HTML correctly but silently 404 on CSS and island JavaScript in production - while local testing passed and the page still returned 200. That combination is the worst kind of bug: nothing in your development loop tells you it is happening, and the status code says the page is fine. The fix embeds generated assets directly into the server bundle for Vercel deployments, so they cannot get lost between build and static hosting.
The same release also closed a prototype-pollution-shaped hole in asset lookup, where a request to a path like /_stoneware/toString could accidentally resolve to an inherited JavaScript object method instead of a clean 404. Nothing exploitable was demonstrated, but a lookup that can return an inherited property instead of your own data is a class of bug worth removing rather than reasoning about.
Both were caught by an edge-case test sweep, not by the initial build. Stoneware now runs 500+ automated tests, and every bug fix ships with a regression test that fails against the pre-fix code - which is the part that matters. A test written after a fix, that was never seen to fail, proves nothing.
Where it fits - and where it doesn't
Stoneware's sweet spot is anything where most of the page is content and only part of it is interactive. If you're building something closer to a browser-based app, a client-heavy framework is still the right tool, and I would rather tell you that here than have you find out three weeks in.
Stoneware isn't trying to replace those frameworks. It's making a deliberate bet for the much larger category of sites that don't need one.
| Good fit | Reach for something else |
|---|---|
| Documentation sites | Collaborative editors |
| Blogs and publications | Real-time dashboards |
| Marketing and landing pages | Browser games |
| Product catalogues and business sites | Anything with a heavy client-side state model |
Try it
No config required to get HTML-first, secure-by-default, and a client bundle that's 0 bytes until you actually ask for one.
bun create stoneware my-app
cd my-app
bun dev- ✓Source: github.com/stoneware-dev/stoneware-core
- ✓If you try it, I'd genuinely like to know what breaks - that's how the last two releases got better.
