There’s a joke that goes around frontend Twitter every few months: every year is the year of “maybe we should stop shipping a megabyte of JavaScript to render a list of products.” Everyone agrees. Nothing changes. Six months later we’re all still shipping a megabyte of JavaScript.
I want to push back on the idea that there’s no alternative. At Wexron, we’ve shipped four significant web properties in the last twelve months — this company site, a client’s hotel booking flow, the AutoCom marketing site, and the internal dashboard for Wexron Hosting. Exactly one of those is a single-page app. The rest are Astro or htmx over a server-rendered backend. None of them are worse for it. Most of them are better.
This is the mental model we use when deciding which side of that line a project falls on.
The three questions
For a new project we ask three questions, in this order:
1. Does the user need their edits to survive across route changes without a refresh?
Think Figma, Linear, Google Docs. The distinguishing feature of these apps is that state lives in the browser and the network is an implementation detail of syncing. If you navigate between views, you shouldn’t lose your in-progress work. The answer to this question is almost always no — most websites are not documents-in-the-browser, they’re pages-on-the-server that occasionally submit a form.
If the honest answer is yes, you’re building an SPA, and the right tool is React (or Svelte, or Vue — the framework is the least interesting choice you’ll make). Stop reading this post and go pick one.
2. Is the interaction model mostly: user reads page → user clicks thing → server does something → page updates?
If yes, you’re building a hypermedia app. The network doesn’t need to be an implementation detail, because the page is the state. The browser already has a built-in state management system called “the URL” and it works great.
For this category, the best tool we’ve found is htmx over a server-rendered backend. A button sends a request, the server renders a fragment, htmx swaps it into the page. Everything you’d reach for React for — optimistic updates, loading states, error handling — is handled in a way that’s honest about the fact that the source of truth is the server, not the client.
Our AutoCom admin dashboard is built this way. It has the interactivity of a SPA. It ships about 14 KB of JavaScript, gzipped, including htmx itself.
3. Is the page mostly content that rarely changes per-user?
If yes, you want a static site generator that can ship zero JavaScript by default and only hydrate the bits that actually need interactivity. That’s Astro. It’s what wexron.in is built on. The entire marketing site loads with ~18 KB of JavaScript, most of which is for one animated hero component that we chose to make interactive because it was fun.
The cost of “just use React”
The default advice is “just use React.” It’s a defensible choice — the ecosystem is huge, every engineer has used it, hiring is easy. I’m not going to tell you it’s wrong.
But there are costs that nobody tallies when they reach for it:
- The bundle. Even a well-tree-shaken modern React app is 70–120 KB of JavaScript before you write a line of your own code. On a good 4G connection in Calicut that’s 400+ ms before your components even start hydrating. On the edges of our target audience — customers browsing from Bihar on a 3G connection on a ₹6,000 phone — it’s multiple seconds of a blank page.
- The indirection. Every piece of state lives in three places: the URL, the component tree, and some global store that you’re now arguing with your team about. For a hypermedia app where the server already owns all the state, this is pure overhead.
- The hydration tax. The server rendered the page once, you shipped it to the browser, and then the browser re-runs the same logic to make it interactive. You paid for the render twice. Astro’s island architecture is a direct response to this, and it’s one of the most important ideas in frontend right now.
None of these costs matter for a true SPA. All of them are pure waste for a content site or a hypermedia app.
The migration from React to htmx
Last year we rebuilt the AutoCom dashboard from React+TypeScript to htmx+server templates. The numbers:
- Bundle size: 680 KB → 14 KB (98% reduction)
- Time to interactive on a mid-range Android device: 2.1s → 0.4s
- Lines of code: 8,400 → 3,100 (63% reduction)
- Bug count per month: roughly flat (it was never a bug-count problem)
The line count is the one that surprised me. I expected htmx to be about the same size as React — you’re moving complexity, not eliminating it. But once you stop maintaining a client-side state machine and a server-side source of truth and the sync layer between them, you just delete a whole category of code that was only there because of the impedance mismatch.
The network bill is the other surprise. Because htmx sends HTML fragments over the wire instead of JSON, every response is bigger — a row in a table is maybe 2KB of HTML vs. 400 bytes of JSON. But you make so many fewer requests that the total data transferred on a typical dashboard session dropped by about 20%. It turns out most of our bytes in the SPA world were not data; they were metadata about how to render data.
What I’d still use React for
I’m not anti-React. I use it. There are specific things it’s still the right tool for:
- Canvas-heavy apps. Drawing tools, map editors, spreadsheet grids with custom cells. When you need a lot of coordinated client state that doesn’t map to HTTP cleanly.
- Offline-first apps. PWAs that need to work on a flaky connection and sync later. Service workers are easier to reason about when the client already owns its state.
- High-interaction forms with complex cross-field validation. Multi-step wizards where the rules between fields are non-trivial. htmx can do this, but the round-trip per interaction gets annoying and the UX suffers.
Notice what’s not on that list: e-commerce product listings. Admin dashboards. Marketing sites. Blogs. Booking flows. Most things. The slice of the web that genuinely benefits from a client-side framework is smaller than the slice that currently uses one, by a lot.
The boring tech thesis
Dan McKinley’s “choose boring technology” essay is a decade old and it still reads like it was written this morning. His core point: every technology choice you make costs you a limited resource called “innovation tokens.” You should spend them deliberately, on the problems that actually matter to your product, and default to boring everywhere else.
If React is the right tool for 15% of the web, and you use it for 100% of the web, you’re spending an innovation token on a problem you don’t have. Astro and htmx are so boring that they don’t count — they’re just well-engineered versions of how the web worked in 2004, with the bad parts fixed.
You should reach for them more often than you probably do.