This article is published in English.
htmx vs the SPA Default: When Hypermedia Beats a JavaScript Framework
How htmx attributes replace client-side rendering, what the bundle sizes really mean, and a balanced guide to where hypermedia apps win and where React still wins.
Many teams reach for React on every new project, including admin panels and CRUD tools that are mostly forms and tables. htmx argues that a large share of those applications never needed a client-side framework: the server can send HTML, and a handful of attributes can make any element fetch and swap fragments of it. This guide explains how htmx works, compares its footprint with React's, and lays out both the genuine strengths and the limitations advocates tend to skip, so you can choose an architecture deliberately instead of by habit.
It assumes you have built web apps and have spent most of your time in React or a similar framework.
How the SPA became the default for everything
Somewhere around 2013 the industry shifted its core model. Rather than servers returning HTML pages, they began returning JSON, and JavaScript in the browser turned that JSON into the interface.
For some products the single-page application was a real step forward. Gmail, Figma and Google Maps hold a lot of state in the browser, and their interactions have to feel instantaneous.
The problem is that the model spread to everything. An internal dashboard that lists rows and lets you edit them ended up with the same architecture as a design tool. A marketing site with one contact form acquired a bundler, a router, a state library and a hydration step.
That default has concrete costs:
- Two codebases, often in two languages
- The data model defined on both sides
- A build toolchain to maintain and upgrade
- A permanent need to keep client state in sync with server state
htmx's core claim is that many applications pay these costs without getting anything they need in return.
What htmx is
htmx is a small JavaScript library that extends HTML with attributes. With them, any element can issue an HTTP request and put the response somewhere in the page. That is essentially the entire library.
A live search box shows the idea. The input declares which URL to call, which event triggers the call, and where the result should go:
<input type="text"
name="q"
hx-get="/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#results">
<div id="results"></div>
Read the attributes as a sentence: when a key is released and the value has actually changed, wait 300 ms, send a GET request to /search, and place whatever comes back inside #results. The delay:300ms modifier debounces the input, so fast typing does not send a request per keystroke.
The server does not return JSON. It returns a ready-made HTML fragment:
<ul>
<li>First result</li>
<li>Second result</li>
</ul>
htmx drops that fragment into the target element. There is no JSON parsing, no client-side template, and no client state that could drift from the server.
The attributes you will use most
hx-get,hx-post,hx-put,hx-deletechoose the HTTP method and URL.hx-triggersets the event that fires the request, such asclick,keyup,load,every 2sorrevealed(when the element scrolls into view).hx-targetselects the element that receives the response.hx-swapcontrols how the response is inserted:innerHTML,outerHTML,beforeendordelete.hx-indicatorpoints to an element shown while the request is in flight.hx-confirmasks the user to confirm before sending.
These compose well. The next snippet deletes a table row: the button sends a DELETE request, targets its nearest enclosing tr, replaces that whole row with the (typically empty) response, and asks for confirmation first. The swap:1s modifier delays the swap by a second, which gives a CSS transition time to fade the row out and makes the action feel responsive:
<tr>
<td>Widget</td>
<td>
<button hx-delete="/items/42"
hx-target="closest tr"
hx-swap="outerHTML swap:1s"
hx-confirm="Delete this item?">
Delete
</button>
</td>
</tr>
Notice what is absent: no separate JavaScript file, no build step and no client-side state. The server remains responsible for deleting the item and deciding what the row should become.
Two architectures side by side
The difference is clearer as a flow than as a tooling comparison.
- SPA model: the server sends JSON, client code renders it and holds state, the user acts, the client updates its state, re-renders, and sends JSON back.
- Hypermedia model: the server sends HTML, the browser shows it, the user acts, the server replies with new HTML, and the browser swaps it in.
In the hypermedia model there is exactly one source of truth: the server. That removes a whole class of bugs in which the client believes something different from what the database says, such as stale caches or optimistic updates that were never reconciled.
Carson Gross, who created htmx, presents this as a return to what REST was meant to be. Roy Fielding's REST includes the HATEOAS constraint (Hypermedia As The Engine Of Application State): the server sends representations that include the actions available next. A typical JSON API does not; the client has to know in advance which endpoints exist. HTML does this natively, because a link is a state transition and a form is an action. Whether that framing strikes you as insightful or academic is a good predictor of how you will feel about htmx overall.
Measuring the bundle size, and what it does not tell you
Quoted sizes vary, so it helps to measure. At the time of writing, the minified htmx build weighed in like this:
htmx.min.js: 51,238 bytes raw
16,576 bytes gzipped (16.2 KB)
The production builds of React 19.2.8, the React package plus the DOM client, came to:
react.production.js: 4,446 bytes gzipped
react-dom-client.production.js: 94,757 bytes gzipped
combined: 98,420 bytes gzipped (96 KB)
That is about a sixfold difference, and it covers only the React runtime. A real React app adds a router, a state library, a data-fetching layer and its own components, so shipped bundles commonly reach several hundred kilobytes. The htmx figure, by contrast, is the complete client-side dependency. Exact numbers shift with every release, so re-measure against the versions you would actually ship.
Be careful about the conclusion, though. Bundle size affects the first load, not how fast interactions feel afterwards. On a good connection the load-time gap is small, and on repeat visits both files come from cache. The more convincing performance argument is that htmx has no hydration phase, the stretch during which a server-rendered React page looks ready but ignores clicks until its event handlers are attached.
Where htmx genuinely helps
- One language and one codebase. A team working in Go, Python, Ruby or C# can build the whole application. Validation lives in one place and the data model is defined once.
- No build pipeline. A single
scripttag is enough. There is no bundler configuration, no frontendnode_modules, and no churn of frontend dependency upgrades. - Locality of behavior. What an element does is written on the element itself. Instead of following a click through several files and a store, you read the markup. This is one of the strongest arguments, and it is about maintainability rather than speed.
- State in one place. There is no client cache to invalidate, no stale data, and no optimistic update to roll back.
- Less frontend code for CRUD. Teams moving CRUD-heavy apps to htmx often report frontend code shrinking by 40 to 60 percent. That figure circulates widely without a clear primary source, so treat it as anecdotal, but the direction is consistent across reports.
- SEO and accessibility by default. The output is server-rendered HTML, so crawlers see content and assistive technologies get real semantic markup, as long as you write good markup.
Where htmx falls short
These are the points that often go unmentioned.
Rich, continuous interaction
Drag-and-drop, canvas editors, spreadsheets and interactive charts with brushing and zoom involve continuous interaction rather than discrete requests. In htmx, every interaction is a round trip to the server. For something like a text editor, that is not a trade-off; it rules htmx out.
Latency on poor networks
Proponents point out that edge hosting has cut round-trip times substantially, which is true. Still, a user on a weak rural mobile connection may wait a couple of hundred milliseconds for something React would handle locally in a couple of milliseconds. htmx apps feel excellent on good networks and noticeably worse on bad ones, which is the reverse of how the argument is often presented.
No mobile or offline story
htmx is web-only. React Native lets a team share concepts and a meaningful amount of code with native apps, so if native mobile is on the roadmap, this can outweigh every other factor. Offline use is also off the table, since every interaction needs the server.
Complex client-side state
Multi-step wizards with interdependent fields, forms with live cross-field validation, or screens where several components must react to one change get awkward. Teams typically add Alpine.js or hyperscript, and at that point they are assembling a framework out of parts rather than using one.
A small ecosystem and a smaller talent pool
React has a mature component for almost any widget. With htmx you either build your own date picker or embed a standalone JavaScript one and manage the boundary yourself. Familiarity matters too: at the time of writing, surveys put React usage above 40 percent of developers against roughly 7 percent for htmx, with npm downloads at a ratio of around 560 to 1. That affects how fast new hires become productive and how easily you find answers when stuck.
Reading the adoption numbers carefully
The real picture is more nuanced than either camp suggests.
- React usage is not falling. It remains dominant in absolute terms by a very wide margin.
- React satisfaction is slipping. Survey data has shown usage holding steady while "would not use again" responses rise. People keep using React but enjoy it less, which is a real signal but not the same thing as abandonment.
- htmx growth is relative. It passed 40,000 GitHub stars, added roughly 16,800 in 2024, and topped the frontend category of JavaScript Rising Stars. That is rapid growth from a small base.
The defensible summary: React is not being replaced, but its position as the unquestioned default is weakening. Keep in mind that much of the htmx-versus-React content online is written for search traffic, and figures like the code-reduction percentage or various speed multiples get repeated without attribution. Treat them as directional and check current sources before quoting them.
Choosing between them
Pick htmx when:
- The app is fundamentally forms and lists: admin panels, internal tools, CRUD apps, content sites, dashboards that display data rather than manipulate it.
- Your team's strength is on the backend.
- You want a single codebase that whoever is on the team in a few years can maintain.
Pick React when:
- The browser genuinely holds significant state, as in editors, design tools, real-time collaboration or heavy data manipulation.
- You need native mobile apps or offline support.
- You depend on a mature component ecosystem.
There is also a third option that is often ignored: use both. htmx can serve the CRUD screens while React powers the two or three views that truly need it. Nothing forces one architecture across a whole product, and mixing htmx with an island of React is easier than mixing two SPA frameworks. If you are already on htmx, the htmx 4 upgrade guide covers what changes in the next major version.
React Server Components borrow part of the hypermedia idea by moving rendering to the server. They still need the full React runtime and a Node-capable server, though, so they are not the lightweight option; they are React gaining some of the same benefits while keeping its weight.
Key takeaways
- htmx lets any element send a request and swap in server-rendered HTML, which keeps the server as the single source of truth and removes client-state synchronization.
- Its runtime is roughly a sixth of React's before any React app code is added, but the practical gain is avoiding hydration more than shaving load time.
- It shines for CRUD apps, internal tools and content sites, and struggles with continuous interaction, poor networks, mobile, offline use and complex client state.
- Mixing the two is a legitimate architecture, not a compromise.
The more useful question behind the debate is how an architecture designed for Gmail became the default for a six-field form. Nobody was exactly wrong; a tool became a default, and defaults stop being examined. Whatever you pick, including React, make it a decision you reasoned about rather than one you inherited.