I did.
My startup did.
And now we’re going to rip it all out and move to a React front-end.
HTMX makes response handling much more complex. Every endpoint returns 3–5 different HTML fragments. Frontend and backend must agree on every scenario — success, validation errors, system errors, partial updates, full reloads.
And HTMX is still a fairly obscure library. The documentation and examples are lacking, there isn’t a real set of established best practices at scale, and not for nothing, LLMs aren’t great at it.
React is mature, used at scale, provides separation of concerns, and is great for agentic AI coding. HTMX has its place for simple projects, but for anything non-trivial, it’s a no for me.
I was able to find architectural patters that work smooth as glass.
Here is what my htmx apps have: - Single-purpose endpoints: Each endpoint returns ONE thing (a card list, a tag cloud, a form fragment) - Optimistic UI: Preferences like font/theme update the DOM immediately; the save is fire-and-forget with no response needed - Simple error handling: Most endpoints either succeed (return HTML) or fail (HTTP error code) - No fragment orchestration: Not returning 3-5 fragments; using hx-swap-oob sparingly
Here is how I update fonts on screen in user prefs: User selects font → JS updates body class immediately → htmx.ajax() saves in background → done
vs. the anti-pattern you're describing:
User submits form → backend validates → returns success fragment OR error fragment OR partial update OR redirect signal → frontend must handle all cases
No language or programming paradigm is perfect. All programming is an exercise in tradeoffs. The mark of a good CTO or architect is that ability to draw out the best of the technology selection and minimize the tradeoffs.
Please do a write up of the best practices you've found. I tried htmx a few years ago for a side project and while I appreciated the simplicity of a lot of it, I had trouble understanding how/when to use it and in what ways. I think I was trying to contort it too much into my understanding of api/spa. That or my interactivity needs were too complex for it, I can't tell.
These days, I admit, though, the ship has sailed for me and htmx. My main app frontend is React and since the LLMs all know much more than I do about how to build out UIs and are 100x faster than me, I'll probably be sticking with React.
Here's an example offline-first hypermedia-driven soccer application. The match page is the most complex part of the application and I use Morphdom to just do a diff on the main section of the page. I find hypermedia-driven development to be super simple.
I find React to be really complex. What normally takes me just some HTML rendered from the back end and some interactivity on the front end can be done in just a few lines of code, but React takes 10s of lines of code for what I can write in a single line of code using vanilla js. The virtual dom causes it to make everything state. I always find it odd that people reach for that as their front end. I could understand something like Svelte, but even that is too complex for my needs. But I'm always writing CRUD apps, so there is some complexity but not that much, React just makes something that was simple, super complex.
https://github.com/jon49/Soccer
That is what DATAOS.software is all about. It is a manifesto of my proposed best practices somewhat like Hypermedia Systems, but more hands on, more of a cookbook.
Interesting read. I haven't quite finished it all. I haven't ever needed anything like that when writing web pages with HTMX, html-form (my own), nor htmz-be (another one I wrote based off of htmz but the back end decided where the HTML will go, similar to data-star and nomini). When I write code from the back end and target the front end I can use middleware to common updates.
Here's the most complex app I've made with it. The most complex part of the app is the match page. I just use morphdom for that part of the app and it makes it super easy, I just send the whole main part of the page back and let a dom diff happen.
https://github.com/jon49/Soccer
How do you handle HTTP errors?
Just curious because when I used HTMX I didn't enjoy this part.
I like to return errors as text/plain and I have a global event handler for failed requests that throws up a dialog element. That takes care of most things.
Where appropriate, I use an extension that introduces hx-target-error and hx-swap-error, so I can put the message into an element. You can even use the CSS :empty selector to animate the error message as it appears and disappears.
Usually the default behavior, keeping the form as-is, is what you want, so users don’t lose their input and can just retry.
Honestly? I never think about it. I've never had to. What did you run into? Curious what the pain point was.
I had a site where the user can upload a file < 5MB. There may be a way to check this on the frontend, but for security reasons, it has to be checked on the backend too.
If it exceeds it, I returned 400. I had to add an event listener to check for the code (htmx:afterRequest) and show an alert(), but this gets difficult to manage if there's multiple requests to different endpoints on the page. Looking at it now, maybe I should have configured HTMX to swap for 4xx.
I have something similar on my website, and my solution was to make server driven modal/toast responses.
Allow the server to return a modal/toast in the response and, in your frontend, create a "global" listener that listens to `htmx:afterRequest` and check if the response contains a modal/toast. If it does, show the modal/toast. (or, if you want to keep it simple, show the content in an alert just like you already do)
This way you create a generic solution that you can also reuse for other endpoints too, instead of requiring to create a custom event listener on the client for each endpoint that may require special handling.
If you are on htmx's Discord server, I talked more about it on this message: https://discord.com/channels/725789699527933952/909436816388...
At the time I used headers to indicate if the body should be processed as a trigger, due to nginx header size limits and header compression limitations. Nowadays what I would do is serialize the toast/modal as a JSON inside the HTML response itself then, on `htmx:afterRequest`, parse any modals/toasts on the response and display them to the user.
Good idea, thanks for sharing! Nice design also
hx-trigger in the response header would handle that cleanly. Fires an event client-side; one global handler shows the error.
Similar to wvbdmp's approach but without needing the extension
See https://dev.to/yawaramin/handling-form-errors-in-htmx-3ncg
I have never loved the idea of the server rendering HTML which is probably why I have such a hard time with HTMX. In every other paradigm you clearly separate your server API and UI rendering/logic. Web apps are the only place where it seems common to have the server render UI components. Imagine if you had a Java or Swift application and had the server sending your phone UI screens. I don’t even know how you would pitch that. About the only thing I have seen that makes some sort of sense here is video game rendering to be able to play on really limited devices with quick internet access.
The problem with SPAs is the ecosystem. I recently found packages like this [1] and this [2] in my node_modules and that is insanity. But the architecture honestly makes way more sense than any other paradigm: server side is a well defined API and the client renders UI and handles local state.
[1] https://www.npmjs.com/package/isarray
[2] https://www.npmjs.com/package/is-string
X (the Window System not the thing that was Twitter) explicitly allowed a remote machine to send rendering commands over a network to the local machine? (I'm avoiding using the term "server" as X has this round the other way - from the users perspective the server is local and the client can be remote).
Sun's NeWS also allowed something similar - but with a large amount of programmability using PostScript.
Sure but that was with the idea that everything happens synchronously. TeamViewer also renders the UI remotely. Any system where the client actually does any state management and not just pixel/character rendering does not use this. While the X system did allow this, real world systems did not use any of these remote rendering commands besides “render pixels”.
At the end of the day, personal preferences play a huge role. All programming is a compromise between tradeoffs.
React has a lot going for it. It's simply that I prefer htmx; it feels cleaner to me.
> Frontend and backend must agree on every scenario — success, validation errors, system errors, partial updates, full reloads.
Well, frontend and backend always need to agree on every scenario, that's why I prefer to do validation on backedn and frontend to just display it and not do any validation.
That makes for some nasty debugging and unsafety. Both sides should parse both times, unless you're encountering real (not imaginary) performance issues.
As someone who's been parsing everything entering the system from 2018, I don't believe you can have performance issues by parsing the data entering the system, the only exception I can name in a decade was real time trading app where the data coming in all time was just gargantuan that parsing it all the time was impacting UX and even then there should be an argument for the backend insisting on sending whole data instead of the latest value.
Frontend and backend will always diverge and 90% of your bugs will be around that. Mitigating the divergence is the primary goal of every web framework, both backend and frontend, when you sit back and think about it.
> and is great for agentic AI coding.
Definitely sad to see this everywhere nowadays, tech choices made because of AI
We are locking in path dependence on flawed technologies because by nature newer technologies lack the training distribution needed for good LLM output. Our industry is in a pretty dismal state.
I found the book (hyper media systems) to be better at explaining full integration than the library site, which gives nuts and bolts and big picture, but not much intermediate info.
> Frontend and backend must agree on every scenario
When is this not the case?
I suspect the hidden assumption here is that frontend and backend are written by different people/teams with a narrow interface between them. Because they use different technologies and build tools it’s easy for them to end up being competely siloed, which IMO is one of the worst aspects of how webdev evolved in the last 20 years.
Thus, it’s considered normal that the backend team spits out some JSON and doesn’t know or care what the frontend team does with it.
With HTMX that distinction doesn’t exist so much (the frontend consists of fragments generated by the backend) so you will need a different organizational approach. The “frontend” folks will need to write a lot of “backend” code, but presumably in a separable layer.
It is true that htmx requires some straddling backend and frontend. I think the organizational approaches that split them is dumb to begin with personally. Good UI/UX almost always impacts the backend.
When an API returns JSON, your JS framework can decide what to do with it. If its returning HTML that's intended to go in a particular place on a page, the front-end has far less flexibility and pretty much has to put it in a specific place. Hence why they said endpoints can return 3-5 different versions of HTML.
> When an API returns JSON, your JS framework can decide what to do with it.
The JS framework is the frontend, so you're still coordinating.
> If its returning HTML that's intended to go in a particular place on a page, the front-end has far less flexibility and pretty much has to put it in a specific place.
Well yes, because presumably that's what the app is supposed to do. If it's not supposed to put it in that place, why would that be the specified target?
If this kind of static assignment of targets is not flexible enough for some reason, then use OOB updates which lets you replace fragments by id attribute. That lets you decouple some of these kinds of decisions.
Although "endpoints can return 3-5 different versions of HTML" is also a bit of a red flag that you're not using htmx correctly, generally endpoints should be returning 1, maybe 2 fragments in unusual cases.
In any case, you might find DataStar more to your liking, it's partway between React and htmx.
To clarify, there's nothing React or SPA about datastar. Moreover, HTMX v4 is essentially Datastar-lite (but heavier, and less capable)
There is, Datastar has client-side rendering based on signals [1]. Datastar is also very explicitly designed to be modular and extensible, so you can extend the client-side with more features, as they've done with Web Components.
[1] https://data-star.dev/guide/reactive_signals
Signals aren't even really "rendering". And react and spas dont have a monopoly on doing things on the client - that's just javascript. I dare you to go to the datastar discord and tell them that they're React-adjacent, and SPA-like
"framework can decide what to do with it" sounds like a feature (more flexibility) but is actually often the source of errors and bugs.
A single, consistent, canonical response, generated by the server, taking into account all relevant state (which is stored on the server) is much cleaner. It's deterministic and therefore much more testable, predictable and easier to debug.
For pure UI-only logic (light/dark mode, etc) sure you can handle that entirely client-side, but my comment above applies to anything that reads or writes persistent data.
It's terrible, why would I want my endpoints to return random HTML fragments? I realize thats how you did it in the JQuery times, but I was never in those - at that time we simply had template engines in the backend so this HTML slop wouldn't contaminate everywhere..
Most of the frontend stuff I do is for internal pages on embedded devices, and I'm very happy with a structure where I have the frontend being a full React fancy component lib SPA that is eventually just compiled down to a zip bundle of files that the backend needs to know nothing about and can serve as dumb files. The backend is a JSON API of sorts that I would need to build anyway for other use cases.
Returning HTML sounds like a styling nightmare, if anyone changes the structure, unintended consequences. Plus it’s difficult to reason possible states of the UI with fragments sitting on the server, possibly dynamically built on the server. Very jquery/PHP ish. I had my fun and don’t want to go back.
To "get" HTMX, you have to think server-first. State is persisted on the server and all state-change logic is performed on the server (often triggered by http requests from a "dumb" client).
If you hang on to "possible states of the UI" that are client-side only then yes you'll have some difficulty implementing server-generated HTMX responses but more importantly, when your client has state that sometimes isn't in sync with, or shared with, or validated by your server, you're setting yourself up for errors and bugs that will exist regardless of framework.
In short, HTMX forces you to apply "single source of truth" concepts to your application.
Clearly you haven't used something like HTMX. Do you understand what "returning HTML by the server" mean? You are basically sending back a view, like you would in any other framework actually. This would be the exact same pattern as displaying or dynamically adding a new component from either React or Vue. It doesn't create any styling issue at all, nor any unintended consequences.
I’ve used jquery which is very heavy into html fragments. It can get unwieldy compared to keeping all your rending logic in one place and applying data to it like in React. Other comments here validate the suspicion that HTMX can fall apart in large systems.
Unless you’re saying the components returned by HTMX are using the shadow dom for isolation, you can very easily run into styling problems by changing a style and not realizing some injected fragment of HTML somewhere relies on it being a certain way. I hope you’re using a monorepo because unlike typescript APIs, those HTML fragments and CSS styles are not type checked.
Well yeah, HTMX wouldn't be a good fit for micro-frontends, but I didn't think many people were actually using those. You have to write all your html in accordance with a single stylesheet, but that strikes me as the least of HTMX's impositions.
That's because jquery takes a very global approach to the DOM, which will naturally lead to spaghetti code.
Every backend framework, be it dotnet or Symphony or Rails, has a concept of components. It's a non-issue.
The HTML fragments aren't random.
> It's terrible, why would I want my endpoints to return random HTML fragments?
What would you return instead? It's easy to generate HTML, because that's what your server is already generating (and that's about all it should generate).
HTML is the last thing I would ever want to generate on my embedded device, it's a terribly verbose string-based mess invariably coupled with stylistic choices. Which is why my servers don't generate any of that, they serve static files - and any interactive information in something that looks a lot more like an interface definition.
I don’t get what you’re saying (maybe there’s a typo). With React, generating HTML on the embedded device is exactly what you’re doing — twice (virtual and real DOM).
Okay, so what do you your servers actually serve stuff to?
I kind of don't get why if you want to display something in a web browser you'd generate anything other than HTML.
I think the parent's point is that when you have a react front-end, your back-end basically just deals in structs of data. There's no HTML or templating to think about. It's just JSON-serialisable structs. That makes the code on the back end much simpler, which makes it easier to run in a resource-constrained environment.
The only exposure the back-end has to HTML is streaming the static files to the browser. Which can be done in small chunks.
If your back-end is rendering HTML with every request, it has to do a lot more work. It has to load HTML templates into memory and insert strings into them.
Okay, so how do you actually show the stuff to the end user?
Just raw structs of data? Or do you turn that back into HTML?
Now you've got two sets of templates to cope with...
Why would I care about how much effort it is for the server to generate? It's already generating HTML from templates, and it's more-or-less infinitely capable of doing so.
> It has to load HTML templates into memory and insert strings into them.
In practice, I doubt this is much slower than serializing JSON. Keeping a couple kilobytes of HTML templates in memory is nothing. Conversely, running a whole vdom on the frontend (typically more resource-constrained than the server) is a much bigger performance issue.
Three levels down and people have entirely forgotten what my post was. My "server" is some anemic ARM core built into real physical hardware with 64M of read-only storage. I don't want it spending its time "hydrating" some DOM, I don't want to bring any of this frontend insanity on there at all. No code hosted on npm shall ever run on that processor or I can't go to sleep in peace.
So how do we still get a fancy SPA website? Build it all down to a simple zip bundle, the ARM can serve those static files just fine. The SPA talks to the ARM via a few JSON APIs. Very nice clean boundary.
Yes, if your server is a weak, limited processor, you want to keep the demands on it as low and lean as possible, and let the client do the heavy lifting. HTMX is not a good fit for this scenario, just like PostgreSQL is not a good database to embed on your devices.
This isn't a controversial idea and nobody would try to sell you on HTMX for your use case.
1. No, templating strings is actually quite cheap. I'm doubtful that you could benchmark any substantial difference between templating html and serializing json.
2. Who has a server with a weak, limited processor? HTML templates power Django, Rails, and PHP. This paradigm worked fine on the servers of 20 years ago, in the slowest languages we use. I could serve a Django app on my phone and see reasonable performance.
I agree that templating is very fast and efficient, probably faster than serializing to JSON.
Read the OP's posts - he is talking about a "server" being an embedded device with 64mb of read-only storage. My assumption is that the data output format is basically hard-coded in the device's OS and doesn't even rely on JSON serialization.
Oh wait, oh my god
> Three levels down and people have entirely forgotten what my post was.
I missed this reply entirely. Whoops.
That said, I do feel like you can do HTML templates on a tiny chip with 64 megs of memory. I've seen NASes with comparably tiny & terrible chips serve their web UIs this way: paper-thin html templates with <form>s for interactivity and <table>s for layout.
But that's the point of something like HTMX, though.
You draw a simple web page with very basic elements, tag them with an HTMX element, and let the client side javascript turn that into something that "does stuff".
I wrote a phone directory with find-as-you-type using Django (because it's what I had lying around) and HTMX (because I read somewhere that it was cool and fun and I should try it, and I bow easily to peer pressure), and then min.css to make it not look shit.
All totally vendored, just download the appropriate .js and .css file and check them into your source control.
When you type it hits an endpoint that returns a bit of HTML that contains a table, and swaps it into a div. The querying part and the drawing part is client-side and there's nothing stopping you passing a query string to the endpoint and getting just a bare table out.
Indeed there's nothing stopping you detecting if it's an HTMX request and only returning the fragment, or if it's "bare" returning a full valid page. You know what? I should do that, I'll log a feature request on my project for it.
I've gotten a little away from the original point.
You use HTMX on the client side, to turn a plain HTML page with no interactivity into something that will pull data from a backend and swap it into the DOM. If you want to return JSON and render that with yet another library you can, but you're probably already filling in the blanks in an HTML template as it is.
My understanding is that HTML templating is often cheaper server-side than JSON serialization.
What's npm got to do with it?
Why can't your code fill in the blanks in some HTML template instead of filling in the blanks in some JSON?
What's the difference between rendering HTML and rendering JSON?
Why are you then offloading rendering HTML from JSON to a painfully slow scripting language on the client?
This is plain false.
Real world examples with error handling would be great for HTMX. Now in the LLM era you might get away without those. I just don't understand why can't we have complete documentation for the most basic scenarios.
So first you choose a nicher solution you didn't fully understand and now you jump into another one (full of issues but popular) you likely don't understand the trade offs too?
Just to be completely clear... you do not need React just so you can turn JSON into HTML. HTMX can 100% can do that.
You're argument is fine assuming you wish to become another react frontend in a sea of react frontends.
But the documentation example is a terrible argument, the benefit of HTMX is it is easy to understand what is actually happening. There is no magic, you don't need to dive through millions of lines of code to figure out what this is doing like react. It's very basic javascript. Just read the library, frankly you don't even need any documentation. Just take 15 mins and read the entire library.
> become another react frontend in a sea of react frontends
Whats the big deal here?
> Every endpoint returns 3–5 different HTML fragments. Frontend and backend must agree on every scenario — success, validation errors, system errors, partial updates, full reloads.
And why would that differ from React?
When I was building a website with React, I needed to model a "apply coupon" endpoint with different states (coupon applied, coupon does not exist, coupon exists but has reached its max usage limit) and it was so annoying because you needed to
1. The backend route that returns JSON with a different model depending on the coupon state
2. The JSON models for each response type
3. And then on the frontend you need to load the data, parse the JSON, figure out which "response state" it is (http status code? having a "type" field on the JSON?) convert the JSON to HTML and then display it to the user
In my experience it added a lot of extra "mental overhead". It is something that should be extremely simple that ends up being unnecessarily complex, especially when you need to do that for any new feature you want to add.
When using htmx, a simple implementation of that would be
1. A backend route that returns HTML depending on the coupon state
2. Some htmx attributes (hx-post, hx-swap) on the frontend to make the magic happen
Don't get me wrong, there are places that you wouldn't want to use htmx (heavily interactive components) but that's why htmx recommends the "islands of interactivity" pattern. This way you can make the boring things that would add unnecessary complexity when using React with htmx, and then you can spend the unused "mental overhead" with the interactive components. (which, IMO, makes it a more enjoyable experience)
At the end of the day it is just choices: Some people may prefer the React approach, some people may prefer the htmx approach. All of them have their own upsides and downsides and there isn't a real answer to which is better.
But for my use case, htmx (truth to be told: I use my own custom library that's heavily inspired by htmx on my website, but everything that I did could be done with htmx + some htmx extensions) worked wonderfully for me, and I don't plan on "ripping it all out" anytime soon.