Quick aside: almost 2 years of silence
Initially I wanted to publish such a blog post a year ago. It had been a crazy year since DjangoCon Europe 2022, and I thought that writing some follow-up a year after that talk would be relevant. Well you know what, the year after that has been even crazier. I am so exhausted that I had to quit the awesome company called Contexte after more than 7 years.
Now here I am: taking some time for myself, launching this blog , and finally taking, with this post, the opportunity to answer some questions that have arisen in the many discussions around htmx. Sorry that took me so long.
The “Mother of all htmx demos”
A brief history
- 2021: working on a SaaS product with a fairly sophisticated user experience, I am frustrated by seeing how our React front-end is bulky, and by not being able to fix it because I am a “back-end developer”
- Late 2021: learning about the hypermedia approach, watching Chris McCords introducing Phoenix.LiveView back in 2018, learning about Hotwire and Unpoly , I decide to clone our front-end, just to see where the limits are, using Django and htmx .
- 2022: after we decide to switch from React to htmx because my proof-of-concept was convincing, I think about telling our story and how it made us happier. My talk proposal is accepted by the awesome DjangoCon Europe event.
- Late 2022: the video of my talk is published, Carson Gross himself highlights it on the htmx website , gives it this flattering “Mother of all htmx demos” nickname, and makes a buzz around it from lobste.rs to the orange site front page , pushing the video to 30k views in a few months (80k+ today).
- Summer 2023: famous twitcher/youtuber ThePrimeagen makes a video of his own where he hilariously comments mine and where he adds some good points and asks some good questions. Today his video has been viewed 310k times, pushing my talk to almost 400k views in 1.5 year, which is crazy.
Impact on htmx
This graph of the evolution of htmx Github stars makes me immensely proud, because I feel like my work had an impact on my industry, which seems to have been making wrong decisions for almost a decade:
Impact on my own Github profile
Without me doing anything, my Github profile is now followed by hundreds of people.
- Makes me feel responsible, this is why I am so sorry for being so late in the publishing of this follow-up.
- Also makes me feel proud, especially because many of my followers come from developing countries, where building a team may not be as easy as in EU or in the US. I hope that the hypermedia approach makes it easier for people in these countries to launch their SaaS products.
Some questions or statements I’ve read and would like to answer to
“Your JS dev must have been a terrible JS dev”
I’m not interested in judging the guy. We had our issues, but I don’t see them as the primary cause of the failure that our front-end was.
He was constantly running after two things:
- The Javascript ecosystem (breaking changes, security updates, new approaches, new silver-bullet promises…). A vibrant ecosystem is nice, but it can also be overwhelming.
- The complexity of client-side application state management when the single source of truth about application state is the server.
That did not let him enough time and energy to focus on what really matters on the front-end side when you’re building a B2B SaaS product: the UX.
Maybe some more talented developer would have done it better. But exceptional talent has an exceptional cost, and that Contexte could not afford. And would the resulting implementation have been more efficient than the one I made with htmx? Not sure.
“You must hate Javascript”
(Oh boy I’ve seen that one a lot)
Of course not, who would hate it? Javascript is the only programming language available on the client side of the web platform, and it’s getting better and better every year! I use it whenever I need pure client-side interactions, as “the right tool for the job”.
I do not make UX trade-offs in order to avoid using Javascript. I did not engage the React-to-htmx migration to avoid writing Javascript code, I did it to enhance our UX while reducing our overall costs.
“With a server framework like Django, CSS and JS are probably second-class citizens of your codebase, and your JS is probably written like old jQuery plugins”
The “front-end” ecosystem worked really hard these past years on improving tooling, why would anyone refuse to take advantage of that?
- The Javascript parts (mainly Stimulus
controllers), are written as ES6, with nice
import
statements, etc. It may not be the latest trend, but it’s modern, efficient and testable. - The Javascript dependencies are declared in a
package.json
file in order to easily manage updates through something like Dependabot , and avoid CDN usage. - esbuild takes care of transpiling the Javascript to something that works on every browser chosen by the team, and bundles the whole thing in a timely manner.
- The CSS parts are written as SCSS, with nice variables, nesting and mixins.
“But how does that integrate with Django and its old-fashioned static
folder?”
Now that’s where the magic happens. The original idea came from Tim Kamanins django-tailwind integration
: simply put the JS/CSS/SVG/PNG files in a static_src/
folder of each Django app, then build them with esbuild using the static/
folder of the corresponding Django app as the output folder.
So let’s say you have a web page where you want to use a Dropdown
Stimulus controller which code lives in another Django app. Let’s look at how it’s done:
The controller, which is reusable for all our Django apps, lives in ui/static_src/components/dropdown.js
:
import { Controller } from "@hotwired/stimulus"
class Dropdown extends Controller {
...
}
export { Dropdown }
It’s exposed with other reusable components by ui/static_src/components/index.js
:
...
export * from "./dropdown"
...
Now in another Django app, you have a JS file dedicated to some specific page, myapp/static_src/myapp/mypage/main.js
:
import { Application } from "@hotwired/stimulus"
import { Dropdown } from "../../../../ui/static_src/ui/components"
window.stimulusApp = Application.start()
stimulusApp.register("dropdown", Dropdown)
It’s modern ES6, it compiles crazily fast with esbuild, and the result goes to myapp/static/myapp/mypage/main.js
.
Of course, since the compiled JS files live in static/
folder of each Django app:
- At dev time, you get nice auto-completion when using the native Django
{% static %}
template tag to refer to them - At commit time, you can avoid adding them to git with a simple
.gitignore
- At build time, they are seen by Django native
collectstatic
management command - At deployment time, they take advantage of Django’s awesome
staticfiles.storage.ManifestStaticFilesStorage
to get filename-based cache-busting for free (cf. docs )
Now, can this be called “Old-school JS as second-class citizen”, or “Modern JS as a first-class citizen”? You tell me.
“What about Locality of Behavior (LoB)? Why Stimulus and not Alpine or Hyperscript?”
Because:
- Both Hyperscript and Alpine make your HTML heavier (especially when implementing some sophisticated behavior that is replicated is multiple places), which is not good when HTML is sent over the wire.
- Hyperscript means learning a new language, which has a cost.
Stimulus, on the other hand, gives us nice standard ES6 code that’s easily auto-completed, linted, tested, reusable, etc. Writing Stimulus controllers is kind of always the same, almost boring, but it feels so safe. As for what LoB is supposed to bring (an immediate view on the code triggered by a function call or a user action), the right tooling can provide that: with the right plugin in your IDE, when you see data-action="click->mycontroller#mymethod"
, Ctrl+clicking mycontroller
gets you to the right class in the right JS file, and Ctrl+clicking mymethod
get you to the right method.
“Why not Tailwind?”
(This is a bit off-topic but ThePrimagen mocked us here , so I had to answer 😉)
Because:
- Tailwind makes the HTML your styling language, while it’s supposed to be your HyperText (some text with special ability like links/forms and, thanks to htmx, the same magic of links/forms applied to anything).
- Tailwind makes your HTML heavier, which is not a good thing when HTML is sent over the wire.
- Tailwind means learning a new language, which has a cost.
- CSS is easier than 10 years ago.
- Naming CSS classes isn’t horrible since BEM , and is even quite nice since A-BEM
“Why not a global event bus?”
(here again, ThePrimagen)
You know what’s a global event bus? Your browser. The Javascript standard Web API exposes a global event bus, and the Hypermedia approach is nothing more than leveraging this global event bus to update the server-side application state and make your web application react to application state changes.
“Are you still using htmx, or at least the hypermedia approach?”
Yes.
To be completely honest, when it comes to the past 2 years at Contexte:
- We didn’t get to implement any new UI that would be more complex/interactive than what I demonstrated during the DjangoCon talk. Contexte is a media, and most of its web development consists of CRUD and advanced CSS, so the hypermedia approach (aka HTML over the wire, aka server-side templating) is very well suited .
- Sometimes htmx has shown some caveats when trying to apply it to user-generated content where we wanted some links to be boosted but some others not. But with a few lines of Javascript we were able to achieve quite the same feature, so this doesn’t disqualify the hypermedia approach.
- At some point Contexte needed to implement a custom real-time collaborative editor ; obviously this was one of these use cases where the hypermedia approach is not the right approach, so it has been implemented as a Vue app, and that story is told there .
The point is: I would recommend it anytime, to any team that is working on a SaaS product with no intent of real-time collaboration or no client-side-first application state. Every day I use SaaS products that would benefit the Hypermedia approach, with lower latency, less bugs, smaller costs…
“But what if I need a mobile app? What’s the point of htmx if I still need an API? I don’t want to have 2 back-ends…”
Many ways to answer that:
- Do you really need a mobile app? Wouldn’t a PWA do the trick? (I will probably talk about this in a separate post later…)
- Do you really need an API for your mobile app? I haven’t tested Hyperview yet, but since its creator wrote the hypermedia.systems book with Carson Gross, I would give it a try…
- Okay you do need a mobile app and you think it must be done the traditional way, with an API, then please be informed that:
- Nowadays, implementing a JSON/HTTP API has a very low cost: django-rest-framework , API-Platform , probably many others in any programming language used to serve dynamic content on the web, not to mention PostgREST . All these amazing technologies allow you to create an API in a declarative way.
- That JSON/HTTP API would not consist of a separate back-end ; it would be implemented in the server-side framework you already have, and hosted on the web servers you already have. And as htmx has no cost at all, you would not pay for two different ways of serving contents to you web app and your mobile app.