Address
aadhar.shGarageBreak-even at three submits

Break-even at three submits

htmx shipped 4.0.0 on the day this page was written, and it is a real improvement on 2.x: 36,716 bytes minified against 50,917, with the history cache dropped and XMLHttpRequest traded for fetch. The size is the easy question. The one that decides adoption is how much of your existing JavaScript the library can delete, and that turns on whether your code is already shaped like a request and a swap. This site's mostly isn't. So the measurement went to the one page where htmx should have won, and it lost by a factor of three.

The library is priced per visitor, never per repo

A hypermedia library moves rendering to the server and sends HTML fragments back. That trade is good when a page holds many interactions, because the download amortizes across all of them. It is bad when a page holds one, because the visitor pays the whole library to save one response.

Which means the honest unit is bytes per visitor rather than bytes on disk, and the number you need is the count of swaps a single visitor performs. Everything below is that arithmetic, run on real bytes.

The page htmx was built for

This site has 54 documents and 17 client modules, and almost none of it fetches HTML. quiz.js makes zero network requests. So do hoist.js, infotip.js, notepad.js and nav-tray.js. The modules that do fetch read JSON and paint structured interfaces from it, which htmx has no opinion about.

One surface is different. /coffee is a booking form: two plain method="POST" forms, server-rendered responses, 874 bytes of inline script for the slot picker. Submitting it reloads the document. That is the shape htmx was designed for, so that is where the experiment went.

What was built

htmx 4.0.0 vendored as a client asset, loaded on the booking page alone rather than on the shared shell, so the confirmation and decline pages pay nothing. The form keeps its method and action as the no-JavaScript fallback and gains three attributes:

<form class="book" id="bookform" method="POST" action="/coffee/book" novalidate
      hx-post="/coffee/book" hx-swap="outerHTML" hx-disabled-elt="#submit">

The Worker grew a branch that answers the body alone when htmx asks for it, and the full document otherwise, which is what a crawler and a visitor with JavaScript off still receive:

function bookingReply(req, env) {
  return req.headers.get("HX-Request")
    ? new Response(successFragment(env), { headers: htmlHeaders() })
    : new Response(successPage(env), { headers: htmlHeaders() });
}

The build passed with no changes to it. The minifier left htmx byte-identical, since it arrives minified.

The diff

Both trees went through bun run perf:snapshot, which self-builds through the wrangler dry run and reports Brotli q11, the quality the edge serves. Base is 3f9f2f9f. Every number here is deterministic, so an unchanged file produces no row at all.

categorybaseheaddelta
client assets, Brotli86.35 KiB97.81 KiB+11.46 KiB, +13.3%
htmx.jsabsent11.46 KiBnew
Worker bundle, gzip239.88 KiB239.97 KiB+0.09 KiB
pages, Brotli571.62 KiB across 54 filesno change
dictionary deltas110 deltas, 498.41 KiBno change

The page bytes hold still because /coffee is rendered by the Worker rather than staged as a file, so its markup lives in the bundle. The whole cost is one new asset.

What the swap actually buys

This is htmx doing its job well. The confirmation response stops being a document and becomes a pane:

POST /coffee/bookrawgzipBrotli q11
full page, today17,0015,1014,187
htmx fragment515291219
saved per submit16,4864,8103,968

95% off the wire on that response. If the argument were only about response size, htmx would win this page outright.

The arithmetic that closes it

11,735 bytes of library against 3,968 bytes saved per submit is a break-even at 2.96 submits per visitor. A booking form is submitted at most once, so the third submit never arrives.

Move the sliders. The library is a first-visit cost, because the asset is cached afterwards, and the saving lands on every submit whether or not the visitor is new. Those two facts are the whole model, and they are enough to find the region where htmx wins.

library
saved

Per 1,000 visits, Brotli bytes. Library cost is 11,735 per first visit; saving is 3,968 per submit. The booking rate would have to pass the crossover for htmx to pay for itself at the current repeat rate.

The default positions are a guess about a personal booking page, and they are the part of this page you should distrust most. The site records no funnel for /coffee, so the 4% is an estimate rather than a measurement. Drag it and see how far it has to go.

Three reasons the rest of the site is worse

The booking page is htmx's best case here. The surfaces that carry the actual JavaScript are worse, for reasons that have nothing to do with the library's quality.

Rendering would move onto the scarcest resource

htmx returns HTML fragments, so every swap is Worker CPU. This account runs on the Workers free tier, where CPU under sustained load is what has actually taken surfaces down here: a signature fan-out across 28 probes clamped requests at 10 ms and blacked out the lens for a window in August. Client rendering off a payload already in memory costs the platform nothing.

The caching model runs the other way

The photo tooltips read /images/exif.json, which is 55,674 bytes raw and 2,764 Brotli, warmed once when the browser goes idle, and it carries the metadata for all 158 photographs. Under htmx that becomes 158 separate fragment requests, one per hover, each one a Worker invocation and a round trip. The site front-loads bytes into assets that go nearly free on the second visit, which is the opposite bet.

Boosted navigation is slower than what already ships

41 documents here carry a speculationrules block at moderate eagerness, so the next page is rendered before the click lands. hx-boost is a fetch on click, which is strictly later. The cross-document View Transition was removed from this site in July for the same reason. htmx 4 also drops the history cache and refetches on back navigation, where the browser's own bfcache restores the page with its state in place.

What the snapshot cannot see

A wire-size diff measures files. It cannot measure round trips, and round trips are where the argument above actually lives. So the diff proves the narrow claim, which is that the library is additive on this site's one hypermedia-shaped page, and the three reasons remain reasoning rather than measurement.

Two smaller things the experiment left on the table. htmx landed as a plain top-level asset, outside the content-hashed /a/ tier, so it received no shared-compression dictionary and the delta count held at 110. Giving it that treatment would make repeat visits nearly free and leave the first visit exactly where it is. And the inline slot picker survived untouched, because an aria-pressed toggle and a toLocaleString button label are not requests. Lines of JavaScript this deleted: zero.

What would change the verdict

A surface on this site with several swaps per visitor. That is the whole condition. A multi-step form, a filterable list somebody actually pages through, a comment thread: any of those move the submits-per-visitor number above three and the arithmetic flips on its own.

The finding that would falsify the page is a measurement showing the fragment saving is larger than 3,968 bytes on a real interaction, or a Worker rendering cost near zero under load. The first is easy to imagine on a heavier page than this one. The second is the claim the free-tier CPU ceiling makes hard.

watching · the understanding check is part of the page, not a gate

garagewatching2026-08-28