Building a single-page app with Htmx
jakelazaroff.com
jakelazaroff.com
in other words, this todo app is just about the most complex of a frontend you can easily* build with htmx, i'm afraid. try to fix either 1 or 2 and you end up building components and your own little framework.
as an exercise to demonstrate, try taking OPs code and adding a count of todos on each All/Active/Completed tab that should update every time u add/edit/delete the todos. see how much extra ui code that takes. compare with equivalent [framework of choice] impl (in most it will just involve 1 state update, thats it). this is htmx's explosion of complexity that makes it not [ optimized for change ] (https://overreacted.io/optimized-for-change/). code that is hard to change eventually calcifies and consumes code that is easy to change if you do not consistently garbage collect (nobody does)
i bought the hype too until i tried building something nontrivial in htmx and im afraid the aforementioned islands of interactivity you can build are very very smol islands indeed.
happy to revisit my opinion if there are componentlike design patterns in htmx i am not aware of.
*emphasis on easily; with enough elbow grease u can do anything ofc. but then you fall out of htmx's very narrow [ pit of success ](obligatory codinghorror dot com link)
I choked reading this imagining people thinking they're doing something simple (as in not complex) by introducing websockets so they can keep state in their Go backend and sync it with their front-end, ya know, to keep track of the # of TODOs checked.
Edit: SSE*
If you mean SSE, then yes that would work just as well (unless you need the bidirectionality for the client to modify some aspect of the connection after the page has loaded). There is an htmx-sse extension too.
I'm not sure how XHR alone would let you automatically get backend state changes reflected to the frontend. You can poll one or more endpoint, but that's more complicated to get right and less efficient.
Yes, that's basically the idea.
Long polling.
Check out (history of) "Comet": https://en.wikipedia.org/wiki/Comet_(programming)#Implementa...
Also writing a client ontop of an existing HTML client is very very easy so even when not on web it's east to implement...
I think you may mean easy. It may be _easy_, but it's not simple. There are so many more moving pieces, failure modes, operational issues now to consider. Websocket connections aren't free.
As someone else mentioned, SSE is a somewhat simpler protocol that achieves the same purpose. Same idea though.
I hate it when a UI tells me I did an action when really there's an asynchronous background task happening that may fail
https://htmx.org/essays/a-real-world-react-to-htmx-port/
htmx can work well for a reasonably large class of web applications. not all, and certainly not all UI patterns, but w/a bit of a mind shift to the hypermedia approach & a bit of accepting some limitations thereof you can simplify a lot of things pretty significantly.
if you try to shoehorn it in to the SPA mindset, triggering server requests on every interaction, yeah, it's not gonna be great
You could be using only htmx or only alpine but the combo is really nice
One of the most exhausting things about any discussion about front end web dev is that it gets treated like a monolith. It _can_ be incredibly complicated. In many scenarios that complication is unwarranted. But in some it's justified.
Htmx is a poor choice for a full single page app. That's fine. It excels in other areas. Right tool for the right job.
Like I said, this seems like a poor value proposition to me. Of course, if other people are happy with it more power to 'em.
To me it feels like people are eager to latch onto this because it's different, and because there's a dopamine hit associated with seeing the easy thing become a little easier when taking a new path that's overoptimized for it - hence the hype - not because it's actually good engineering, in terms of the tradeoffs you're making.
Of course, I feel the same way about Tailwind, so maybe I'm just old and grumpy.
React -> "The site exists in JS, HTML is just a render target."
jQuery -> "Poke at the HTML from JS." Which is brittle as hell.
htmx -> "The site exists in HTML, extend HTML to handle the common tasks you want to do with it."
Depends what your aim is. Do you want to become a full time front end engineer? Then no, probably focus on other frameworks. But do you, from time to time, want to put small pieces of interactivity on web pages when it isn’t the sole (or even major) focus of your job? Htmx might be ideal.
I don't see how that has anything to do with the choice of htmlx. You always have the freedom to use any languages in the backend. If anything, htmlx imposes a bunch of hypermedia convention on top of your conventional API, regardless of languages.
Back-end devs think htmlx will allow them to be comfy in the back-end and just trash their front end with "thin client" that has no framework or any sort of organization. Eventually someone who is willing to actually think about front-end has to rebuild it and decouple the 2. Happens all the time.
I want to be able to be able to code in a more natural back end setting - I like Raku, others may prefer Go (or Rust or PHP or JS). I am not ready to commit to a complex event driven framework like using the WordPress backend and React on the front end. But I do recognise that a pure browser only model for UX is too crummy … let’s say I want to validate an email … well if I specify input email in HTML, my browser will pop and ugly dialog if there is no @ in the input.
So HTMX gives me my backend coding preference and a clean way to add simply tweaks to the UX. That is the power of HTMX for me.
I don’t think HTMX is a good solution is you want to write Google Maps or Facebook. But it is a great way for straightforward sites to be written and I do not understand why I will be forced to step into JS front end rewrites if I go this route (and I can happily bundle some JS + CSS + HTML into an HTMX response for the islets where I need it.
I think the point the OP is making is that very new frontend frameworks dictate what backend language you use. They just use HTTP requests and consume JSON.
Making hard things hard is a subset of making hard things possible. Better than making hard things impossible, which is the usual result of making easy things easy :)
> And I don't think that's what HTMX provides - it mostly gets in your way with the hard things.
And yet they're still possible. Like I said: hard things are going to be hard no matter what. Even if the hard things are harder, that's still a worthwhile tradeoff for maximizing the easy things.
You can replace elements outside of your direct tree if you want. The simplest case you replace the whole page and pick what elements you actually want to change.
You're thinking about HTMX wrong. It's for progressive enhancement, the default is a full page reload and then you progressively enhance parts of the HTML. You should be using the same code paths and template to generate the islands of interactivity as you do for the whole page. You can then optionally send less HTML by just sending the parts that you expect to have actually changed, your "islands".
And then you end up with modern SSR frameworks that do the bookkeeping for you.
we can basically get the benefits of spa without having to learn js / use minimal js using htmx in some sense
https://github.com/donseba/go-htmx check this out
for the other stuff, the growing browser stdlib has basically replaced most of jquery's usecases. so (in the most non condescending or negative way possible) htmx occupies a very awkward sliver between "The Platform" and Frameworkland and after giving it some time I have yet to see the benefit from keeping any of it in my head
At all.
Every time web sites reimplement basic browser behavior, which they invariably do these days, they get worse, often significantly, because they never get it right.
I think you are overestimating how much 'UI standards' have gone up over time, actually. Nobody except for a small 'extremely online' set of people and control-freak designers care about all those complex UI/UX requirements. Users certainly don't care. They want fast-loading, responsive pages that get out of the way and don't drain their batteries or clog up their connection.
The other thing here is that approximately no one can actually tackle the complexity introduced by SPA frameworks and 'UI standards', and it's far more likely that they bungle it up and make giant, wasteful, UX pitfall-ridden apps. With server rendered pages, plain old HTML, and progressive enhancement, we at least have a better chance of producing something usable.
re: hx-preserve. what if i want to "conditionally preserve" - preserve this element when my state is one way, but not in other states? i dont see a way.
re: hx-swap-oob. looking at https://htmx.org/attributes/hx-swap-oob/ i think it still does not address what i'm looking for. any master-detail list kind of UI will want updates in 2-3 places when 1 piece of state updates (aka ui consistency, ui as a function of state). i fail to see how attaching an attribute with 1 place for an ID solves that. perhaps theres another api for "multiswap"? even if it existed... idk if i'd be comfortable using it man (ofc, i am clearly biased/taught to "think in components" from 7 years of react exp)
You could send the state back to the server with the request and respond appropriately on the backend. Or check in the db without sending it, if it lives in the db (it probably should?).
Htmx does have tools for both of these cases. Out of the box, htmx throws out state, but there are plugins such as morphdom-swap for merging in the new DOM fragment into the old while keeping state. I have some client-only state that holds references to DOM elements in Javascript, and by default, yes, htmx breaks all those references as those elements no longer exist. Link in morphdom-swap, and my references live on across reloads, even across attribute and content changes.
And for #2, htmx also allows you to swap in elements that are not the target element, just by specifying that that's what you want.
IMO these are pretty basic tools of htmx. Like you said, without them about the most complex thing you can create is a to-do list, and sometimes not even that.
this https://github.com/bigskysoftware/htmx-extensions/tree/main/... with 159 stars is a basic tool of htmx? is this the community consensus?
It's an example using django-components. Does this satisfy your comment about component at all?
small. thanks
but i don't get to do the same thing when they say stupid shit like "smol". that's just normal, while i am retarded
So is breaking up things with whitespace which you did reasonably well.
So it's not a matter of "You know what I mean, so who cares?". They may realistically have had to put in significantly more effort into understanding what was written.
That isn't entirely true for something silly like the word smol.
but i digress . whataboutism aside, what's the actual problem ?
More like speedrunning the period between 20 years ago and 10 years ago! React is 11 years old, much older than jQuery was when React was initially released.
There is an entire other discussion about how to keep the web as "information superhighway" (as opposed to an app medium) and accessible. I am sympathetic to that cause, but I think we should create new web standards for that. As it stands, the html/css/js trio is doing too many things and sucks at all of them.
I was just having the conversation yesterday. We were talking about how React Query/Tanstack Query is a bit of a rats nest under the hood. We're looking into ways of taking it out to simplify things a bit, especially since we aren't using a ton of the library's features, nor will ever have a need for them. Found myself saying "I wish the native fetch() had a built in cache layer... oh yeah".
The "standard browser" is pretty much the most expansive standard library that exists.
I am not surprised around (1). HTMX is based on REST, which is by nature based towards having the state in the server and having a stateless client. But what is the advantage of having the state in the client. Isn't that just a bias of the current frameworks?
I can see cases where you really need a thick client, and in that case I would use GraphQL rather than REST for those. But, there are quite a few cases that can work with REST and mostly server side.
I think (2) is solved by out of band capabilities of HTMX. I don't think that is naturally a limitation of the conceptualisation.
This is why HTMX is ultimately an imperative framework. You have to wire everything up manually yourself instead of simply building a declarative representation of your UI and letting your framework handle the synchronization.
Did you ever use jQuery? The negative aspects are remarkably similar to the point that it is mildly amusing.
What is ironic though, is that the morphing algorithms are similar to what modern SPA frameworks do, except that it can be addressed without introducing new concepts such as vdom, zones, signal etc.
I agree that the island architecture is limiting when it comes to large scale application with state shared across multiple areas in the UI. I'd be curious to hear others finding success in building apps.
p.s. I appreciate the smol island :)
* Search box
* Typeahead
* Instantly updating search results
It was super instructive. In the end, I realized HTMX was probably not the best tool for that job, but it really helped me bridge the gap between "I get in theory why we use JS on the FE" and "Ah, I can see why client side JS is the obvious choice for this".
Doesn't a searchbox with typeahead and instantly updating results still require a backend? Isn't that backend not the most important ingredient of this use-case?
Or did you send a large payload of objects to the frontend and have it indexed clientside with e.g. lunr.js? I've used fuse.js for this, but for a use-case where we knew we had less than a hundred documents to index and where the access control was simple and the content reasonably stable. I'd never use this for a search feature in e.g. an admin backend or a large, content-rich webapp.
TBF, probably partially a skill issue. But I know that just going back to a "normal" approach where my backend returned a huge payload and then I wrote the filtering/rendering in JS, was sooo much simpler to think about, even if it resulted in more total code.
At first I was reaching for JS and this feels good—total control! But I wanted to challenge myself to use a hypermedia based approach, and in the end the outcome felt better.
One key insight was that I needed to rerender the form too when I returned the results. At first I only inserted the results into the page and left the form untouched. Later I also realized that if you want to encode the entire search in the url as parameters, then you need a way to render that combo of results and form state on page load anyway.
I guess where it can get tricky is with the templating system. I used FastHTML and wrote all my HTML as Python code. This gives the full expressiveness of the programming language, which is nice, but it seems that templating systems typically provide plenty of logic and control flow. A single template can do a lot. For your example of making only certain regions selectable depending on search results, you could pass the list of regions into the template engine and use a for loop capability in the template.
But hey, you found a way that works and that's great. Just wanted to share that I found with a bit of paradigm shift I found the hypermedia approach really straightforward and clean.
I've built a search page that used meilisearch as backend and a simple "SPA" in vanilla JS once.
It was completely "Hypermedia based" because I stored all the state of search/facets/pagination for the SPA in the URL(query args). A URL is limited in what it can store, but its a neat storage for state, especially because it's limited :)
Then if you landed on a page with certain query args, the state would be rebuilt from that, and the request to meilisearch would be constructed from this "state", sent there, and the objects in the response would be the state for the results.
Once I abandoned the pure HTML/HTMX approach and just focused on returning a big payload with the search results in JSON and rendering it with vanilla JS, the whole thing became a lot easier.
I've rewritten a bunch of my JS framework apps for simplicity and don't miss much.
My method is however to use Django and templates to build a regular MPA, and then switch out link changes (between pages) with htmx functionality so there is no browser reload. At least then you'll have a webapp that acts mostly like an SPA.
Next up, you can add more interactivity using htmx as much as you want (with some kind of Django components, ideally). You can even add VueJS to one of the pages if you want, but full blown SPA frameworks tend to eat into development time, so rather not unless absolutely needed.
A <frame> was a system for loading same-origin html files from an Index. It was the original Single-page multi-page system and at the time seemed good enough for 90% of html 1.0 sites. <iframe> was supposed to replace it but then everyone used it for Ads and closed-source widgets.
These days we basically hack around all this in JS by manually controlling history. Which is brittle as hell, and why so many web apps break "Back" etc.
Managing state is always a hard problem. Versionable extensible persistence is harder still. Now add the requirement that this state has to be captured in a concise but human-readable way, and it's really hard to do in a way that doesn't introduce numerous footguns. I'm not saying that there isn't some good solution, but I don't know what it is, and I don't think anyone has figured this out yet.
site.com/?frame1=photos.html&frame2=books
but then I need a way to continue this pattern for frames inside of frames and these params can only work for flat data. I need a URL tree structure.
Edit:
This syntax seems to work! [] and ; for nested params.
https://en.wikipedia.org/wiki/Cat?frame1=photos[a=1;b=2]&fra...
At the end the standard is never what they exactly wanted. So it's kinda a futile effort, and for what? Most don't write code for 3rd party consumption, where standards shine.
If I'm seeing a list of todos and I've filter actives there's really no benefit into not having the filters reflected in the url. The same goes if I'm navigating some data in the form of some table and I'm, let's say, at page 2.
The practical issue is that very few SPA apps actually do that consistently. Moreover, because it requires a conscious effort on behalf of developers, a fresh new app might be well-written in that regard, but over the years, as "got to fix this quickly for release" cruft piles up, it becomes worse and worse in that regard.
I think the only way to actually make this concept work reliably is to force people to store state in the URL somehow, or at least make that easier than storing it anywhere else.
Fun experiment though, in the true "hacker" spirit ;-)
It was fun for a bit to quickly experiment with how htmx works, but I find it difficult to scale in terms of state.
I do have a slightly more complicated TTRPG live character sheet that I use for a game I run. Its got five users at once and sqlite backend. Its gotten moderately more complicated over the last year and now sports htmx, discord integration, and will soon have SSE for me to push events to their character sheets, like ability point refreshes etc and other fun stuff without them having to refresh the page all the time.
I'm just thankful I don't have to dive into JS frameworks and can mostly stay in html, maybe a little hyperscript.
Then I really wanted data reactivity, so I started using `nanostores`, but I kept wanting more, and eventually added `alpinejs`
And then it got a bit too complicated for a one page microsite so I switched to Astro (for the interactive islands bit)
I'm building https://htmgo.dev to do that with go + htmx, and http://fastht.ml seems like a good contender for python
At that point, maybe stop fighting with service workers and simply use a framework like Vue. It allows html templates to be swapped in, in much the same way. Except you can actually debug it and store in localStorage if you desire.
It is then that they will realize complexity comes from single-page apps. Pushing back against that complexity should result in not building SPAs, which is where Htmx comes in. There are still plenty of very successful web apps that are not SPAs.
If a product owner asks to build a SPA, a developer's job is to stop and ask why. Most likely what the product owner really has in mind can be accomplished without a SPA.