7 ms·
HTMX is hard, so let's get it right
- hackrmn 1y agoRelated: https://htmx.org/essays/htmx-sucks/ https://htmx.org/essays/htmx-sucks/ (on the actual HTMX upstream site)
- alex-moon 1y agoThese kinds of write-ups are so key to driving adoption of a new technology. I'm still not super interested in HTMX but this write-up has done a lot of the work already toward nudging me that way. Well done!
- rapnie 1y agoYes, as I remember it it was a 'back to simplicity' of the early web idea. Rediscover the power of hypermedia. I don't know HTMX well, but am following Datastar [0] which was inspired by it, and their selling points are Simplicity and Performance and take it some steps further than HTMX. The approach does shift logic / complexity towards the backend though. [0] https://data-star.dev https://data-star.dev
- jgalt212 1y agoThe complexity has to live somewhere.
- mbvisti 1y agoyes, in the backend
- jgalt212 1y agoIn general, I agree because it's easier to test and monitor back end code. However, if the complexity also comes along with heavy CPU load, I like to push that out to the client.
- andersmurphy 1y agoExcept you have no guarantees what client side compute is available. Sounds like a fools errand if you are looking for consistent performance.
- totaa 1y agocongrats on the first blog post, been using Go+Templ+HTMX when implementing my first startup I think at least some of these issues can be avoided with a different UI/UX to avoid passing temporal/unsaved data between screens. looking forward to the next instalment!
- throw310822 1y agoI read through this and I don't get it. Recreating an entire form on the backend and swapping it with the current one, and then missing the update of the label status? Then solving this by recreating the entire stepper html at each step, with the added complexity that if it contains something you want to keep "it's a nightmare"? Then having to create a temporary server-side session to store data that somehow the browser can't keep between two clicks? Etc.. it's write web apps like it's 1999.
- junon 1y agoAlso the title itself does a disservice to HTMX; wasn't it billed as sort of "modern frameworks are bloated and difficult, let's go back to Simple"?
- fvdessen 1y agoHTMX is indeed simple and lean, but unfortunately that doesn't mean solving common frontend problems with HTMX is simple either. After a decade of frontend dev and being tired of react/vue/etc, I tried HTMX, wanting for something leaner, but IMHO it has big problems, and I am back to react/vue. The two biggest problems with HTMX is that being fully server side controlled you need to put the whole app state in the URL and that quickly becomes a nightmare. The other is that the code of components is split in two, the part that is rendered on the first time, and the endpoint that returns the updated result. You need a lot of discipline to prevent it from turning that into a mess. The final nail on the coffin for me was that the thing I wanted to avoid by picking HTMX; making a rest api to separate the frontend and the backend, was actually a good thing to have. After a while I was missing the clean and unbreakable separation of the back and front. Making the rest api was very quickly done, and the frontend was quicker to write as a result. So HTMX ended up slower than react / vue. Nowadays react/vue provide server side rendering as well so i'm not sure what Htmx has to bring.
- bookofcooks 1y agoYes, this was what I wanted to get across with the article (although I utterly failed to do so). I think I would stick to HTMX for other reasons (which I've made clear in my top-level comment on this thread), but I now I see it an occasional tool to use for something simple and not long-term. > The two biggest problems with HTMX is that being fully server side controlled you need to put the whole app state in the URL and that quickly becomes a nightmare. Or you can create a large session object (which stores all the state) on the server, and have a sessionId in the URL (although I'd prefer a cookie) to associate the user with that large session object.
- Polarity 1y ago> I originally planned to make a simple non-functional uploader, then progressively bring it from "it works™" to "high-quality production-grade Uploader with S3 Presigned, Multipart, Accelerated Uploads, along with Auto-Saving and Non-Linear Navigation Why is every developer trying to make things complicated?
- andrewstuart 1y agoAll that seems rather ….. indirect. Every step of the way I kept urging him to use JavaScript.
- lmz 1y agoThey could have done all that in one long form with JS for client side progressive enhancement (show/ hide different parts) and that would probably be much easier.
- graeber_28927 1y agoYes, my thoughts exactly. I hate implementing N forms with user state session and navigation, when one big form one the client can hold the state for me, and visual trickery can achieve the same. Whenever I go debug unnecessary state machines, or have to refactor them (to compress the number if steps), I scratch my head half the time, trying to follow the string of thought that my predecessor felt so smart about.
- kissgyorgy 1y agoThis implementation is unnecessary complicated. For the step update, you can use Out Of Band update: https://htmx.org/attributes/hx-swap-oob/ https://htmx.org/attributes/hx-swap-oob/ which works in a way that you can send multiple HTML fragments which can be anywhere on the page and HTML swaps them out. Good for notifications, step update, breadcrumb update, menu highlight, etc... I usually solve the second problem by simply saving the state of the individual input fields, you only need a user session. Depending your use-case, you might need to be transactional, but you can still do this saving everything as "partial" and close the "transaction" (whatever it might mean in the given context) at the last step. Much-much simpler than sending form data over and over.
- bookofcooks 1y ago> For the step update, you can use Out Of Band update I did mention using OOB, but I preferred swapping the entire Stepper because the logic on the backend was just a little bit cleaner, and the Stepper didn't include anything else anyways. > I usually solve the second problem by simply saving the state of the individual input fields, you only need a user session. I believe this is exactly what I did in the article, no?
- devnull3 1y agoThis should be trivial with the HTMX alternative: datastar [1] In datastar the "Out Of Band" updates is a first class notion. [1] https://data-star.dev https://data-star.dev
- meander_water 1y agoHTMX has out of band updates too [0], what's the differentiator? [0] https://htmx.org/attributes/hx-swap-oob/ https://htmx.org/attributes/hx-swap-oob/
- devnull3 1y agoIn DS, with SSE you can paint different parts of the page with ease. So in this case, it can update <form> and <label> separately. So instead of one update the backend fires 2. There is not separate marker or indicator for OOB. I think it is best seen in examples on DS website.
- meander_water 1y agoNeat, I'll give DS a go. HTMX also has the option of using SSE with an extension [0]. I've used this to update the notifications tray for example. You could probably do it for OPs example too. [0] https://htmx.org/extensions/sse/ https://htmx.org/extensions/sse/
- spiffytech 1y agoUnrelated: datastar doesn't use a two-way connection for interaction <-> updates. It uses two unconnected one-way channels: a long-lived long-lived SSE for updates, and new HTTP requests for interaction. I didn't see guidance in the docs for routing one tab's interaction events to the backend process managing that tab's SSE. What's the recommend practice? A global, cross-server event bus? Sticky sessions with no multiprocessing, and an in-process event bus? If a user opened the same page in two tabs, how should a datastar backend know which tab's SSE to tie an interaction event to?
- 1y ago
- karel-3d 1y agoEvery time I attempt to use HTMX and backend-rendered templates because it's "simpler", in the end I always end up doing JSON APIs and something like Svelte. Because all the "simplicity" explodes in complexity 5 seconds later, and it's very user-hostile with the constant reloads. This blogpost affirms it
- evantbyrne 1y agoI think it is right to call out HTMX as being a wrong approach for custom form elements. Try a progressive enhancement approach with something like Turbo, where you can still write JS as needed. This wizard could have been more elegantly implemented by removing the server requests for each step and just showing/hiding fieldsets on progression.
- yawaramin 1y agoUser hostile? Constant reloads are exactly what htmx gets rid of–that's the whole point. If you mean developer hostile–sure, reloading the backend server can be annoying if it compiles slowly, like say Rust. I would say, pick the right tool for the job. If you're going to use htmx, pick a backend language that compiles and reloads very quickly.
- devnull3 1y ago> Challenge 2: Passing data down each step Why not use cookies?
- bookofcooks 1y agoYou can... I guess.
- spiffytech 1y agoCookies are more appropriate for whole-site or whole-session data. There's no natural segregation of "this cookie belongs to this instance of this form". You could figure that out, but the additional moving parts cut down on the appeal.
- devnull3 1y agoCookie is what we make of it. For browser, its opaque data anyway. So, when /upload is requested, the backend in response sets a cookie with a random uploadId (+ TTL). At the backend, we tie sessionId and uploadId. With every step which is called, we verify sessionId and uploadId along with additional state which is stored. This means even if the form is opened on a different tab, it will work well.
- thefreeman 1y agoThats... basically what the guy did? He just put the sessionId in the form data instead of a cookie.
- devnull3 1y ago> He just put the sessionId in the form data instead of a cookie. This does not have the benefit of being usable across different tabs or even closing and re-opening the page. Besides, (a minor point) shoving all the state in the cookie makes code simple i.e. don't have use URL params.
- rtpg 1y agothat form state persistence looks soooo gnarly. Really have a hard time arguing that's better than a client side header
- brokegrammer 1y agoI built my latest SaaS (https://clarohq.com https://clarohq.com) using HTMX, backed by Django. I really enjoy the process because HTMX allows reactivity using swaps and plain Javascript events instead of server side state management, useeffect, and API endpoints. However, it's difficult to get things right. I spent way too much time on some basic features that I could have shipped quicker if I used React. The issue with React though, is that you end up with a ton of dependencies, which makes your app harder to maintain in the long-term. For example, I have to use a third-party library called react-hook-form to build forms, when I can do the same thing using plain HTML and a few AlpineJS directives if I need dynamic fields. I'm not sure if I'll ever build an app using HTMX again but we need more people to write about it so that we can nail down patterns for quickly building server rendered reactive UIs.
- bookofcooks 1y agoExactly my idea. This Github demo was pulled straight out of some work I was doing for the Admin of Prayershub (https://prayershub.com https://prayershub.com). Working on this specific feature (the soundtrack uploader) though, I reguarly asked myself "what if I just used Svelte or SolidJS"? Note, Prayershub uses a regular mix of HTMX and SolidJS, so I can pop-in SolidJS whenever I find convenient.
- brokegrammer 1y agoI've been thinking about loading React or something similar for specific components but still not sure which method I'm going to use for that. How are you embedding SolidJS on specific pages? Or do you actually have a full build pipeline?
- bookofcooks 1y agoIt actually depends on the page. Some pages, I don't use the SolidJS JSX, just its plain-js state primitives. For other page, I'll use full-blown SolidJS (with JSX and everything) for like a popup. Example: https://pasteboard.co/hY35xM7VbATG.png https://pasteboard.co/hY35xM7VbATG.png Now, how I specifically embed SolidJS, its pretty simple, I have my entrypoint files for specific pages: assets/admin-edit-book.tsx assets/admin-edit-song.tsx assets/single-worship.tsx assets/worship-edit.tsx Then I have a 30-line build script that invokes esbuild along with esbuild-plugin-solid (to compile JSX to plain-old html, no fancy virtual dom) to compile the scripts into javascript. I can share the build script if you'd like. It helps that SolidJS is so self-contained that it makes such a setup trivial.
- bookofcooks 1y agoHey, author here! Ask me anything! I want to make the intent of this blog post extremely clear (which tragically got lost when I got deep into the writing). I love HTMX, and I've built entire sites around it. But all over the internet, I've seen HTMX praised as this pristine perfect one-stop-solution that makes all problems simple & easy (or at least... easier than any framework could ever do). This is a sentiment I have not found to be true in my work, and even one where the author of HTMX spoke out against (although I can't find the link :( It's not a bad solution (it's actually a very good solution), but in real production sites, you will find yourself scratching your head sometimes. For most applications, I believe it will make ALMOST everything simpler (and lighter) than traditional SPA frameworks. But for some "parts" of it, it is a little tricker, do read "When Should You Use Hypermedia?" [1]; In the next blog post (where we'll be implementing the "REAL" killer features), I hope to demonstrate that "yes, HTMX can do this, but it's not all sunshine & rainbows." --- On a completely separate note, one may ask, then, "why use HTMX?" Personally, for me, it's not even about the features of HTMX. It's actually all about rendering HTML in the backend with something like Templ [2] (or any type-safe html templating language). With Templ (or any type-safe templating language), I get to render UI from the server in a type-safe language (Golang) accessing properties that I KNOW exist in my data model. As in, the application literally won't compile & run if I reference a property in the UI that doesn't exist or is of the incorrect type. You don't get that with a middle-man API communication layer maintained between frontend and backend. All I need now is reactivity, and htmx was the answer. Hope you understand! [1] https://htmx.org/essays/when-to-use-hypermedia/#if-your-ui-has-many-dynamic-interdependencies https://htmx.org/essays/when-to-use-hypermedia/#if-your-ui-h... [2] https://templ.guide/ https://templ.guide/
- exiguus 1y agoThat was really good reading. Lately, I've often heard the statement 'HTMX is frontend for backend developers.' What do you think of that quote?
- bookofcooks 1y agoHonestly, I think the frontend for backend developers has always been those simple Multi-Page Applications. I know they're not the hottest new thing, but they've been around, they work, and they've had time to integrate deeply into langauages & browsers (think PHP for example). Maybe it's more accurate to say, "HTMX is frontend for backend developers who want a SPA."
- bargainbin 1y agoI’ve attempted to use HTMX a few times (as a React-hater, its hype lures me in) and every time I’ve come away feeling like I’ve wasted my time implementing a subpar solution. From reading this, I’ve decided I will never attempt to use it again. All I could think was, just use Go’s HTML templating. What is HTMX adding, really?
- bananapub 1y ago(out of interest: did you ever write web apps before ~2010 or so? the answer to your question seems so obvious to me I'm not even sure where the confusion could come from, unless you've only ever written web apps that are "JS frontend, JS backend, magic communication between them, it's often not clear where some bit of code is running"). HTMX here is making it so the page works without doing a full HTTP form submission + page load for each stage of the "wizard". instead, you write some HTMX stuff in the page that submits the form asynchronously, then the server sends back the HTML to replace part of the page to move to you to the next step in the "wizard", and then HTMX replaces the relevant DOM nodes to make that so. Go's templating is completely unrelated to any of this happening on the front end - it's just generating some HTML, both the "whole page loaded by the browser normally" and the "here's the new widget state code", and so obviously: > just use Go’s HTML templating. is incorrect.
- the_sleaze_ 1y agoI believe what he's saying is either embrace the full power and maintenance of a JS client side framework, or just use ssr'd templates. I agree with them, that HTMX isn't attractive because it doesn't solve any issues I find important. I largest of which is I don't "hate" react and I don't attach emotionally to any arguments about "frontend"
- jmull 1y agoHTMX is the same approach as old-school server-side templating, except you can replace parts of the page with HTML returned from the server, rather than always the whole page. This allows for some nicely responsive interactions, but introduces complexity. I'm not the previous poster, but it's a fair question whether the maybe faster responses justify the complexity. In many cases it probably would not. (Actually, I suspect it's rare; if you know how to make partial page responses fast for HTMX, you know how to make full page responses fast and don't necessarily need HTMX, up until your page just gets too large overall.) The general problem with HTMX is that, by default, the page state, as a function of the initial page plus the accumulated user's interactions with the page, live only in the user's browser tab. This can seem fine for a while, but opens up some fairly fat edge cases (this article covers some of them). There are ways to handle this, but it's additional complexity and work. Maybe someone has or will create an HTMX-friendly server-side templating framework to take the grunt work out of it, but you still have to wonder if one of the numerous existing full page templating mechanisms might not still be superior, overall.
- masfoobar 1y agoI enjoyed reading this. It follows a similar experience with our first htmx website, away from using modern frontends, or just simple jQuery with ajax json data. I remember, working with a co-worker, we planned out the process (a step-by-step on the application like this post) and it made sense to me - but this journey was much harder for my co-worker. Why is this? Simply because he is familiar with MVC pattern of sending json data back and forth, and getting the frontend to update and render it, etc. The idea of html flying with htmx as the behaviour (inside html tags) was just too much. For me, I always preferred the old school way of just letting the server-side generate the html. All htmx does is adds extra functionality. I tried hard to explain that we are sending html back, and to break things down one at a time but each new task was left scratching his head. In the end, our website had somewhere around 20-50 lines of javascript! Much smaller footprint than over 400 lines in our previous project (that being generous). Sure, our server side code was larger, but it was all organised into View/PartalView files. To me, it made really good sense. In the end, I dont think I won htmx over with my co-worker. As for another co-worker, who had a chance to build a new project with htmx, decided on some client javascript tool instead. I dont think I got a legit answer why he did that. With all this above, I learned that some (perhaps most... perhaps all) struggle to adapt to htmx based on their many years building websites a particular way, with popular tools and javascript libraries. Overall htmx does not really change anything - you are still building a website. If anything htmx just add an additional layer to have website really work. Yoda's words now have new meaning :- "No! No different. Only different in your mind. You must unlearn what you have learned." For some its just not happening. I guess Luke Skywalker really shows his willpower, being able to adapt to htmx easier than others. :-)
- ksec 1y agoI remember someone on HN puts it really well. There is a whole generation of developers that thinks frontend = React. And more importantly they are in much larger number than those of us who went through DHTML and Ajax. We are now the minority, and they are the norm.
- owebmaster 1y agoIn 10 years, the people that knows React will be the minority compared to the ones that only know vibecoding.
- npilk 1y agoHTMX is amazing for simpler web apps. If you have a ton of complexity, need to manage a lot of state, etc., I can see how it would be frustrating trying to get everything to fit into HTMX's patterns. In fact, it might actually increase complexity. But, if you have something smaller and want to make it more interactive, React is way overkill and HTMX is a breath of fresh air. I think a lot of the arguments over HTMX come down to this difference. The people that love it see how much better it is for their use case than something like React, while the critics are finding it can't replace bigger frameworks for more demanding projects. (Here's an example interface made with HTMX. IMO React would have been overkill for this compared to how simple it was with HTMX. https://www.bulletyn.co https://www.bulletyn.co )
- PaulHoule 1y agoI think you need to make peace with OOB if you want to enjoy working with HTMX. You need to have a framework such that you can draw a partial inside the page when you send the whole HTML document but also render and draw a group of partials to update several things that change with a request.
- benhurmarcel 1y agoI’ve DIY’d that in Python/jinja with some helper functions. Do you know of any templating language that includes this?
- bilinguliar 1y agoIt is a nice post. A point of improvement would be to name fields idiomatically. Author should run a few Go linters.
- mtlynch 1y agoreturn pox.Templ(http.StatusOK, templates.AlertError("Name cannot be empty")), nil Oof, an HTTP 200 OK response with a body that says the request actually was not OK. I like htmx, but this is probably the weakest part of it. htmx is supposed to let you write semantic HTML, but it's obviously not semantic HTML/HTTP to respond HTTP 200 to incorrect user input. But I think OP is doing this because if they had responded HTTP 400 - Bad Request, htmx would have thrown away the response body by default.[0] [0] https://htmx.org/docs/#modifying_swapping_behavior_with_events https://htmx.org/docs/#modifying_swapping_behavior_with_even...
- SwiftyBug 1y agoI think this debate is as old as time. I can see value in arguments both for and against this pattern. It's just a pattern. GraphQL does the same thing. As long as the consumers of that endpoint is aware of that behaviour I see no harm. In most cases, these endpoints serve only the frontend of the app. I would avoid doing something like that for a public API though.
- deleted 1y ago[deleted]
- naasking 1y ago> htmx is supposed to let you write semantic HTML, but it's obviously not semantic HTML/HTTP to respond HTTP 200 to incorrect user input. It's perfectly semantic HTML, it just doesn't have to be semantic HTTP (as in, you don't have to push semantics to the HTTP level, keep it at the HTML level). Thinking that HTTP status codes should be semantically meaningful means you're still thinking of htmx endpoints are or must be API endpoints. That's a mistake.
- tacitusarc 1y agoYou are, however, communicating over HTTP. If the protocol conventions cannot be used due to limitations of the encoding, it is reasonable to question the encoding design. Another comment asserted HTMX can handle this, it just needs to be configured to do so. If that is the case, then I don’t see an actual issue.
- vFunct 1y agoHTMX needs an easy way to update multiple named targets at a time. That's my current biggest problem with it.
- deleted 1y ago[deleted]
- Doxin 1y agoIt can, using OOB. E.g. returning this as a response: <div id="foo" hx-swap-oob="true">yes</div> <div id="bar" hx-swap-oob="true">possibly</div> will make HTMX update both the #foo div and the #bar div already on the page.
- sgt 1y agoGreat stuff and same applies to Django of course.
- draw_down 1y ago[dead]
- thevivekshukla 1y agoI've been building something with HTMX since the last week, I have not done whole lot of complex things with it but I don't think it will pose any problem when time comes. I get the premise of HTMX and when and why to use it, it's not solution to everything however it is a blessing for backend developers' who wants to work on frontend. -> A bit of backstory For my project Daestro[0], which is bit complex (and big) I chose Rust as backend and Svelte (with Sveltekit) as frontend SPA app. This was my first time working on both. After years of working on Django, I wanted to try statically typed language, after some research and trial, I chose Rust. Sveltekit was obvious because it made sense to me compared to other frameworks and it was super easy to pick up. After working on Sveltekit for a year, I realised I've been spending a lot of time doing these same thing: 1. You create the api on the backend 2. then you create Zod schema on the frontend for form validation 3. the create +page.ts to initialize the form 4. in +page.svelte you create the actual form and validate it there itself with zod before sending it to the server Hopping over two code bases just for a simple form, and Daestro has a lot of forms. I was just exhausted with this. Then HTMX started to get a lot of traction, I was watching it from a distant but having worked with Django and it's template, I was dismissive of it and thought having separate frontend is best approach. -> Why I'm leaning towards HTMX now? - Askama (rust crate) is a template engine which is compile time checked - Askama supports block fragments[1], which is you can render certain part (block) of template, plus for HTMX usage - Askama's macro almost don't make me miss Svelte's components - Rust has amazing type system, now you can just use it, no need to replicate those on Typescript - same codebase, no more hopping - only one binary to deploy (currently for Daestro I've 3 separate deployments) -> My rules for using HTMX You must self-impose a set of rules on how you want to use HTMX, otherwise things can get out of you hand and instead of solving a problem you'll create bigger ones. These are my rules: - Keep your site Multi-page Application and sprinkle some HTMX to make it SPA like on per page basis - make use of hx-target header to only send the block fragments that is required by HTMX (very easy with Askama) - do not create routes with partial page rendering instead a route must render complete page, and then use block framents to render only what is being asked in hx-target - Do not compromise on security[2] [0]: https://daestro.com https://daestro.com [1]: https://askama.readthedocs.io/en/stable/template_syntax.html#block-fragments https://askama.readthedocs.io/en/stable/template_syntax.html... [2]: https://htmx.org/docs/#security https://htmx.org/docs/#security
- gr4vityWall 1y agoI liked the article a lot, thanks to the author for writing it and sharing it. At that point, personally I think it'd be easier to use Preact with a no-build workflow for those bits of the app that have a lot of contained logic themselves, and don't necessarily require a round-trip to the server. I wouldn't use HTMX for that specific use case.
- farmeroy 1y agoI've been building a largish webapp with htmx and I've leaned into web components for these more complicated interactions. I've found htmx great for everything that _should_ involve a call to the backend, anything that does need to fetch data or perform some crud operations, then i can return the necessary markup with oob swaps etc. and mostly forget about client side state But yeah it's great to see people sharing their approaches!
- gr4vityWall 1y agoSounds like an interesting use! Did you write about it anywhere? Or have a repo we could look at?
- librasteve 1y agogreat article can’t wait to steal this uploader for my https://harcstack.org https://harcstack.org project [HTMX, Air, Red and Cro]
- tremon 1y agoI don't really understand the example problem. Why is the first problem statement "each step is a <form> that calls an endpoint like /form/step1 and swaps out itself with the returned form", and not the (to me) more obvious "each step is a page in a tabset and on successful submission, the active tab is automatically advanced"?