In this article, we will aim to understand why you should consider HTMX as a replacement for React next time you choose a tech stack for a web app. We will look at the complexity and the challenges that a traditional HTTP JSON API + React brings and how easily you can avoid them by using HTMX.
NOTE: In this article I will address to React but it can be replaced with any other Front-end framework like Angular or Vue or Svelte or Solid, but I talk about React because it's the default technology that most of the web developers default to.
What even is HTMX
If you don't know already, HTMX is a small browser (JS) library that extends HTML with a few attributes that allows you to update parts of a web page with some response from the server. It also enables HTML to make HTTP requests on all the verbs not only GET and POST.
What problem does React solve
React is a JavaScript library that helps you write highly interactive applications by keeping the user interface in sync with the state. You tell it how to render a given state, and every time the state is updated, it will re-render ( as efficiently as they can ) the UI to reflect the state changes.
Every time the state has changed, you notify the library that it changed and you provide the new state and it will deal with the UI updates.
Examples of high interactive apps that needs local in memory state can be one of the various text editors that you can find on the web (VSCode), a drag and drop kanban board as Trello or JIRA, a video player or a chat room.
What is not an example of such an app? The to-do list you are building, the news website you are reading, the blog you are posting on and most of the websites around. If we were to look at the 80/20 rule
80% of effects come from 20% of causes and 80% of results come from 20% of effort.
You can argue that 80% of the web apps that uses React don't have the need of a local state. And from those 20% that needs it, you can argue that it's only a small portion of the app (about 20%) and the rest can be expressed only in HTML.
The numbers are made up, I don't have any research to back this up
What react also solves that made it widely adopted for modern websites
HTML is old and outdated. The old ways of making applications with HTML involved a collection of pages, links and forms that describes to the user the current state of a given resource and what they can do to change it.
Every time the user interacted with a resource, the application could only reload the whole page to display the new state of the resource.
A couple of years later FaceBook introduced React, a JS library that allowed developers to create single page applications (SPA). No more full page reloads when navigating and cool transitions for state updates, interesting feedback to the user and other niceties that made the web developers adopt SPA Frameworks for their websites.
The Complexity Issue
If you don't understand the above schema, don't worry, there is nothing to understand. I asked ChantGPT to generate it for me, and since it's over-complicated and it doesn't make any sense, it perfectly reflects the current default infrastructure for a modern web app.
One cool programming principle is KISS which stands for Keep it Stupid Simple, or how some might like to joke, Keep it Simple, Stupid!
Current infrastructure and tech stack that modern developers defaults to create web apps is extremely complicated, doing a lot of things that it doesn't have to, just because it's cool!
And works fine, when you are building the first POC by yourself, but the next moment you add more team members, and an agile way of working with multiple iteration and "embracing" the changes, it kind of breaks, for the reasons we will take a look down the line.
The State Management Problem with traditional HTTP JSON API + React
What you often have to do in a web app is to get the state of a resource from the database and present it to the user. Let's take the example of a task management application. The user has a list of tasks, each task has as a state:
- The title of the task
- A description
- A flag if the task was completed
- A due date (optional)
We usually store this state in a database and to present this information to the user you have to:
- Get all the tasks from the database where the user has access to.
- Optionally transform the data (maybe you store the date it was completed and you compute the
is_completed
flag from that). - Serialize the data into JSON.
- Fetch the data via an HTTP request.
- (optionally but usually) validate the data against a schema, probably with YUP or ZOD.
- transform the JSON into state and store it in a cache using Redux, Zustand, react-query or another state management library.
- Transform that state in HTML usually figuring out what the user can do with the data.
In a nutshell, we are describing how to render all the possible states of all the resources in JavaScript, download the said JavaScript in the browser, then the JavaScript downloads a bunch of data in JSON format and render it (if it knows how) on the browser as HTML!
This is a lot of work to show a list of tasks to the user, especially when the tasks only changes when the user changes it and to do so, the app has to put the app in a loading state, make another HTTP request (to PUT or PATCH or DELETE) invalidate the cached value (the state) and re-fetch it to display the changed task.
Or even worse, when the user changes some task, optimistically update the local state and show the change right away and perform the request to update behind the scenes only to notify the user that the update failed after they sow it successfully updating.
This is extremely error prone. It might work well for this to-do app where you are the only developer and the app is mall enough that you can keep a mental map of everything that is happening. But when you have a larger team, especially when you split the team between front-end and back-end, a lot of issues can arise from miscommunication.
The back-end might use the is_completed
flag while the front-end might expect an is_active
flag. The back-end might send the description
processed from markdown to HTML while the front-end might expect it to be unprocessed. The back-end might make the description
optional to allow the users to save drafts while the front-end is not in sync and you see a lot of Uncaught TypeError: Cannot read properties of undefined (reading 'toLowerCase')
On the other hand, on HTMX, you render the HTML directly on the template, as typesafe as your backend language allows it. You send only the relevant information to the browser, you present the user with the appropriate controls on the resource and you instruct the browser or HTMX how to interpret the user actions and the backend response to those actions. All the application state is the HTML, a concept known as HATEOAS
The need for documentation for traditional HTTP JSON API + React
In order to have two teams (back-end and front-end) work independently and communicate via HTTP JSON API, you need to have proper documentation of the API. You also need to document how to calculate what actions a user can take on a given resource in order to display the controls.
Most of this kind of documentation is a pain to write, especially because usually it's required to write before it's implemented, when the developer doesn't yet fully understand the scope of the problem so the front-end can be developed in parallel. This usually ends up with many updates while in development to adjust for the problems that arises during the development and can lead to miss-aligned versions between the teams.
You also need to version the API and be careful to not introduce breaking changes on a non major version changes. You can no longer change the name of a field without bumping the major version. You also need to ether keep multiple versions of the API running or force the front-end team to adapt.
And most of the time, the documentation get's outdated. Some must be urgently fixed, some new requirements come the day before release and now your documentation is out of date, even for a short period of time. And you have to remember to update it, or even worse, you create a ticket to remember it, and somebody else picks it up that doesn't have the whole picture and documents it wrong!
The duplicated logic issue
For each resource, you have to implement authorization policies. You must determine if the current user can mark the task 46234 as completed. Somewhere in the back-end code you must write this check. Otherwise you leave your app open to insecure direct object reference, or anyone with Postman can mark your task done.
You also have to implement the same logic in the front-end, to show the mark button only if the user has the rights to mark is as complete (let's pretend that you can share your tasks with other users, but only you can change them).
Now every time this logic changes, you must implement it in both applications, and release it at the same time or have multiple versions of the API.
The performance issue
In order to have a website rendered in the browser with React, you need to bundle together the react code that has a gigantic footprint on memory and parsing/processing impact, the state management library code, toe UI library code, the CSS-IN-JS library code, the application code and whatever js library we install and use with NPM (and we are not shy to install a new package, see the leftpad problem). This results in usually chunky JavaScript assets to be delivered via network. Sure, you can cache in the browser, but on modern agile development, you deploy at least once per sprint, so that solves nothing. This consumes network traffic and battery, a much ignored problem for mobile devices.
The above mentioned JavaScript needs to be interpreted by the browser, thus consuming processing power and battery.
The JavaScript, especially the ReactDOM, needs to keep track of a mirror of the DOM. Add the normal DOM on top of that and the local state cache, and all the render functions, and all the useMemo
and useCallback
and useState
. Also add all the closures that needs to keep in memory all the context variables. And JavaScript engines are not known for their memory efficiency! You hear people lamenting about how much memory the browser consumes, but they underestimate how much that comes form the websites they visit.
All those adds up and you end up draining users batteries and memory. Sure, you can put the effort and optimize all that, or use another library like Svelte, but all that effort can be put into delivering more meaningful features for your users.
The need for server side rendering
In recent years, we sow the rise of the server side rendering specialized frameworks like Next.js
. Their popularity highlights the needs for content to be delivered in HTML format, especially for accessibility optimization, performance and search engine optimization reasons.
You don't want to wait for the browser to download the JavaScript to render the page, then wait for the JavaScript to make HTTP requests to get the contents and then render it, you want it to be rendered right away, especially for the above the fold content.
This adds another layer of complexity, including:
- The infrastructure, now you need another server for the front-end app too
- The code is more complex, including the mental map of what code runs on the server and what on the browser
- The deployment pipelines are now more complex
- The testing infrastructure is now more complex
- Troubleshooting an issue is now harder, you need to understand if the issue is on the browser, on the client app server or on the API server
Solving those problems
The web development community, each on their own language or technologies they develop on, tied to solve those problems in different ways:
- Next.js (and Nuxt and alike)
- React server components
- Laravel
- Inertia.JS
- Livewire
- DotNet
- Blazor Pages
- Elixir
- Phonix LiveView
- Rust
- Leptos Server Functions
And many other solutions that I forgot about or never heard about!
Anyway, the existence and popularity of such solutions is the proof that those issues are valid and encountered in the daily life of a web developer. Otherwise they wouldn't go out of their way to solve them, especially in an open source manner!
There is also Turbo and the frameworks who adopt them, Ruby on Rails, PHP Symphony and possibly others that solves the same issue in the same manner as HTMX. And the choice for HTMX is only a personal taste in this, but you should definitely learn about this, this is as cool as HTMX!
Among all those, HTMX stands out, not only because it doesn't lock you in to a specific technology, you can switch from PHP to Rust with minor changes to the templates, but to the fact that it completely removes the need for stateful components, or the need to keep track of a certain state of the app that is not resource related.
For example, let's take a confirmation dialog modal. What you usually end up doing, is that you have a local in memory state if it's open, and display it to the user based on that state. In HTMX, the state IS THE HTML meaning that when you click on open modal, you GET the tasks/{taskId}/confirm-delete
and embed the response HTML in the DOM. And when it's deleted, you delete the modal and the task altogether! This solves all the above mentioned problems in an unique and extremely simple manner, you don't need to:
- keep track of the state
- know how to render the dialog
- document the API
- check if the user can delete the task ( in the front-end)
- your back-end app is always in charge
- you get better security as you don't send irrelevant data to the browser and sneak sensitive info
- you get better performance
And most importantly, you keep your app dead simple, and allow complexity only when it solves user problems!
You just instruct HTMX where to get the dialog from, and where to put it, and it's all handled!
<!-- the delete button -->
@if ($chirp->user->is(auth()->user()))
<form>
@csrf
@method('delete')
<x-dropdown-link
:component="'button'"
type="submit"
hx-get="{{ route('chirps.confirm-destroy', $chirp) }}"
hx-swap="beforeend"
hx-target="closest .chirp"
>
{{ __('Delete') }}
</x-dropdown-link>
</form>
@endif
<!-- the dialog template -->
<div class="modal fixed z-10 inset-0 overflow-y-auto flex justify-center items-center bg-black bg-opacity-50" style="backdrop-filter: blur(14px);">
<div class="bg-white rounded p-6">
<h2 class="text-xl border-b pb-2 mb-2">Confirm Action</h2>
<p>Are you sure you want to delete this chirp?</p>
<div class="flex justify-end mt-4 gap-4">
<x-secondary-button _="on click remove closest .modal" >
Cancel
</x-secondary-button>
<form>
@csrf
<x-danger-button
hx-delete="{{route('chirps.destroy', $chirp)}}"
hx-target="closest .chirp"
hx-swap="delete">
Delete
</x-danger-button>
</form>
</div>
</div>
</div>
this example is from my tutorial on [HTMX with Laravel](https://dev.to/turculaurentiu91/laravel-htmx--g0n, check it out!
And just like that, we instruct HTMX, when we click on the delete button, to do a GET request to the chirps/{chirp}/confirm-destroy
and put the resulted HTML before the closest parent <div class="chirp">
ends (at the bottom). And in the delete dialog, when the user confirms, we instruct HTMX to do a DELETE request, to the chirps/{chirp}
endpoint, and when successful, we delete the closest parent with the chirp
class.
Conclusion
In the ever-evolving landscape of web development, it's refreshing to see tools like HTMX that advocate for simplicity and a return to the basics. By leveraging the power of HTML and HTTP, HTMX allows developers to create dynamic web applications without the complexities and overhead of traditional JavaScript frameworks.
So, next time you're starting a new project or considering refactoring an existing one, give HTMX a try. You might be surprised at how much you can achieve with so little.
Top comments (51)
Once upon a time there was : mavo.io from Lea Verou.
Nothing really new in this world
Juste for smiling...
Nice, never heard about that!
E un piacere :-)
So now my API will return this mixture of HTML, cool. what if I need to use the same API for a backend website, a mobile application and some third party companies use that API too?
This HTMX trend is one more thing that contribute to the JavaScript fatigue. more of the same.
You ain't goona need it
In short, imlementing features because you might need them in the future is bad, but if you need them right now, then go ahead and use API JSON. I am just presenting an alternative!
YAGNI feels at odd with "Design with change in mind".
You can keep the api still running and pass the results to the HTML that will be returned to the server. I guess you get what I'm saying here.
The question is how well it Integrates with existing frontend engines. The strict limits between a static page and an spa disappear more and more. Sometimes a great portion of a webapp is static while a certain area or page is very complex, state-driven and highly interactive.
This creates the situation that most of the time react is way too much for the static stuff but necessary for the complex stuff.
In such a scenario I would definitely try a hybrid setup with ssr+htmx for the static stuff but stateful react for the complex ones.
How well would that perform?
You are not limited in any way by HTMX, you can have react parts in your website, or web components or whatever. The only issue you can get is when a part of the DOM of a react component is updated by HTMX and the component is re-rendered, react will override those changes.
But you can easely overcome by not doing that and communicate between the two worlds using browser custom events.
You can definetly submit a react form using HTMX, it will work. You can have, for example, a form dialog modal rendered and managed by react, but when you submit it, have it submitted using HTMX!
But I might suggest that you give a look at alternatives like Alpine.js or Lit web components or Solid.js for a more lightweight approach for reactive scripting.
Thanks for the detailed and thorough response 👍
The shift from client side rendering back to mostly server side rendering is apparent of lately, but it is justified as from what I've experienced front-end frameworks like React were used way too much without a real need.
You're spot on on the complexity of front-end stacks increasing, its now even more complicated with SSR.
One thing I would add, for some of the problems you mentioned between front-end and backend teams, I wouldn't say its related to react or any other front-end framework, as the efficiency between the teams depends on the setup, usually its not a problem, as API contracts are defined beforehand, and the front-end team has Open API docs to generate all interfaces from.
Great read anyways!
I absolutely love HTMX! I am currently using it with Astro 3.0 and some hyperscript as well. With Astro I can use a mix of HTMX, React, Vue and a few other frameworks since it uses Vite for the build. With the OBB-SWAP I am able to update multiple DOM and make my components as interactive as needed with ease.
HTMX convert many interaction form JS to
hx-
tag system. The casual webpage this system will pass work to server, which is really fine, but give extra works to server side programming or other words front end developer need to work much closer to backend developer collogues, what do you think?Of course I saw the potential in HTMX, but I think my React thinking is much closer to
qwik
solution, which is also give a good answer to minimalize the client size JS code size.Yes, I agree that HTMX tightens the link between back-end and front-end, what I would argue is that it's a desired outcome IMHO, as it will reduce the need of interface agreements and documentation. You can acutally spend those resources on implementing stuff that brings value to your users!
And for the server workload, it might be true, but when is the last time when you fetched only the data you needed from your API?
Most of the time you overfetch, you don't only fetch the user name and email to show it in the picklist, you fetch their profile, their employee profile, their employer data, their department, their department budget and maybe what they eat for luch yesterday 😄
With HTMX we are evolving. Just backwards. I hope the next boom will be for the plain HTML or some good old template engine
The problem is that the HTML and the browser API didn't evolved enough to allow native SPA like websites, but that started to chage with the new transition API developer.chrome.com/docs/web-plat...
I really liked this post! I’ve been exploring more and more about JavaScript libraries that simplify front-end interactions and make web apps more maintainable and performant. HTMX has many benefits, to be sure. But, as with any tool you choose, there are some tradeoffs.
Here are a few:
As always, it will vary based on the needs of your project, your team’s expertise, and the particular features you’re looking to build. Here are three instances where HTMX might be preferred:
I recommend reading this article from Facundo Corradini that expands on the benefits and limitations of HTMX: scalablepath.com/front-end/htmx
It is really exciting that libraries such as HTMX are becoming more popular and people are starting to share this knowledge widely.
As a frontend developer, this HTMX does not attract me even a little bit. It seems annoying to work with and I'm passing.
great content. ty
HTMX provides a fine grained update of page elements. In theory, this reduces the ammount of data to be served and should make your applications fast.
But what about latency on the web? Each request takes some time to be answered. What happens, if your connection is slow or not stable? We often see that fetch-requests may delay your page update. In HTMX, fetching little parts of your page is the backbone. Does this harm the user experience?
If you're app has to be usable offline and you have to keep track of state changes, it's outside of the scope HTMX (alone) solves.
Good point! One argument for HTMX are short response times, and I´m really curious whether this is actually achieved in everyday life. I would not like to wait for every second click because my line is busy...
I actually haven't seen much of a difference in response times, but I'm still in an early exploration phase with HTMX and only using it for private stuff.
Personally I feel it's a bit more geared towards projects that don't use JS/TS in the backend and just need some "ajaxy" reactivity. E.g. I can just render all my templates and HTML partials/fractals/components in the backend where I fetch/generate the data (using the template engine of my backend), return it as a response and be done with it. So as a mainly Go/PHP dev it's quite nice and easy to test/validate expectedHTML vs renderedHTML via string comparison xD.
It seems, HTMX is very open to hybrid approaches. Maybe we should use different technologies together to build UI´s:
I´m still not clear if there is any easy way to manage those pages.
Yes, there is no getting around scripting, but you can take a look at alternatives as Alpine.js, hyperscript or even Lit before you decide to render everything in a vDOM and introduce all the react stuff to your project.
I'm playing with the websockets extension right now on a project where my go client has a few 100 open connections to exchanges, aggregates the data into a Redis cache and serves the price aggregates back into a frontend in 10ms intervals. I'm still fiddling with some stuff, but so far its looking quite promising ... I basically have almost no JS remaining and everything is done via the hx attributes.
I also like this part (c&p from docs):
What about DX?
React-Server-Components
HTMX + golang templates - seems like the best choice to skip React (for backend engineers)
How does HTMX allow to test components in isolation (storybook in React ecosystem)?
As a first argument, HTMX will help you reduce the amount of code from houndreds of thousends to only tens of thousends.
linkedin.com/feed/update/urn:li:ac...
And if you want to test, you can test it the same way you test JSON API, you call the endpoints and insepct the resulted HTML.
And about HMR, you don't have any build step. You can just refresh the page or have your backend technology of choice refresh it for you and you have your changes presented in the browser.
Still no HMR?
So you are okay to ignore improvements to developer experience (biggest advantage of react) ?
You are okay going back 20 years in time to how software was developed earlier?
I can remember 10-15 years ago how I used Java -> GWT (Ui lib + js) compiler to develop GUIs and I can tell you now that it sucks for business logic heavy, highly interactive web apps.
You want to see how a different color/font/padding looks like? You have to wait for full page reload and THEN still have to click through 3-5 elements to get to the same ui state to see how it looks. Repeat min 100x per day. It SUCKS
If you are a Java dev then Spring Boot has live reload with dev tools, which would mimic HMR of React.
Live Reload will never mimic HMR because you lose the local state in your Browser.
This is not important for low interactive (nearly static or single SSR rendered) sites but this is highly important for high interactive web apps.
Example: You open a dialog, type something into a form input to see if an error message shows up -> HMR reloads changes without your losing state (dialog keeps open, input retains value) while live reload throws everything away. With live reload you have to open the dialog again and type something in to the input again, repeat that 100x a day and you will start the value in HMR.
I mean, I was were, 15 years ago with SSR rendered/generated/transpired websites with Java -> GWT, I am not going back.
I had never heard of HTMX before. I'm going to try it.
Thank you for a very detailed article, by the way.
No offense, but the reason react exists is because we found out through frameworks like angular that extending the html syntax compared to just using JavaScript syntax is a bad idea... Not only does it force developers to learn yet another syntax, it also means that you have to reinvent the wheel on every problem that was already previously resolved by JavaScript view libraries...
No offense, I had to.
But this is actually about using JS and overly complex FE frameworks all the time. Not everyone loves JS. Some of us would like to create a decent web app in his loved programming language.
Html is not a programming language though... I'm aware that there are many groups who are religiously against react or even JavaScript frameworks in general. Personally I don't really believe in forcing my religion on anyone. All I can say is, there's a reason why JavaScript frameworks were created and why they became popular. If you disagree with the reasons those frameworks exist, by all means, just use HTML. Why even invent HTMX? It is really just another JavaScript framework that uses a (not)HTML syntax, like angular JS.
Most of the Backend dev community not using JS often (me either). But they want to develop web UI-s efficiently (possibly in their programming language of choice). This is the best tool for that with a templating engine. Minimal simple abstraction and great performance due to hx-target and hx-swap.
As FE engineers wants to replace part of BE with Next.js for example.
BE engineers would love to replace some of the FE needs with HTMX. (+ hyperscript or AlpineJs)
More details here in this great interview with the creator of HTMX: Behind HTMX : Carson Gross
Aha, so that's what it is about. BE engineers feel threatened by NextJS and JS framework encroaching on "their" territory? My stance remains the same. Instead of being scared of change, why not research what makes these JavaScript frameworks so popular? There's a whole history and actually really valid reasons behind it :)
Nah, Javascript makes it just over-complicated now. We don't need to stick to javascript because there are a bunch of good programming languages. Javascript is just like a hammber of a metaphor "hammer and nails".
There are not that many languages in the browser.
Thanks for mentioning Turbo!
Great Tool together with Stimulus