Comparing React and Htmx for Building Chatbots

cckolon1 pts0 comments

Comparing React and HTMX for Building Chatbots

***Try both apps here (HTMX) (React). See the code here.***

If you’re interested in frontend technology, you may have heard of HTMX or read the excellent “Hypermedia Systems.” HTMX aims to take advantage of the web’s original design as much as possible, with some extensions to make additional, interactive behavior possible. Because of this, data API endpoints in HTMX return HTML rather than JSON or XML, and this HTML is swapped directly into the browser rather than interpreted in client-side code.

It is common to hear some criticisms of HTMX, like:

It does not allow rich, client-side interactions.

It increases load time by sending markup instead of just the data the user needs.

It leads to a complex, tightly coupled codebase.

These criticisms are true under certain conditions, and there are some things for which HTMX is obviously unsuited (for example, maps). But many applications on the web are mostly for reading text, so HTMX can be a really good choice.

There are also some things about JavaScript web frameworks that seem bad, intuitively. I’ll talk about React here because it’s the industry standard and it’s where I have most of my experience. React apps tend to reinvent a lot of the native behavior of the browser. For example, custom client side handlers often manage form submissions, useState hooks often keep track of form contents, and frameworks like React Router mimic browser navigation while actually doing something totally different on the inside.

Despite this, the industry consensus seems to be that modern apps require enough client side interaction to make a framework like React necessary for basically everything. This is why most frontend jobs require experience with a JavaScript framework.

I want to challenge this consensus by comparing two identical apps, one written with React and one written with HTMX, and explaining with data why HTMX is more suitable.

Chat apps

The app I wrote is a simple chatbot using the OpenAI API. You can read the code and run it locally here or access it on the web (HTMX) (React). Here’s a demo.

I wrote one version of the app in React. I wrote a second version in HTMX with the Server Sent Event (SSE) extension. Both have FastAPI (Python) backends.

The apps look pretty much identical, but something quite different is happening behind the scenes with both. Here’s how your interactions are processed in both apps:

Initial page load:

React : The user is served a blank HTML page, which then populates data by hitting data API endpoints.

HTMX : The user is served exactly the HTML of the main page, with the data baked in.

User clicks on “New chat”:

React : The onNewChat handler fires which sends an asynchronous POST request to the /api/chats endpoint, which returns an ID. A client-side navigation takes the user to the chat page at /chats/. A client-side stream handler opens up the stream to the server, waiting for chat events.

HTMX : The form sends a POST request to the /chats endpoint, which creates a new chat in the database and redirects the user to /chats/. This page has a on it that contains information about the stream in the sse-connect and sse-swap fields, and the SSE extension takes care of the rest.

User submits a message:

React : Message goes to /api/chats//messages/ via POST request.

HTMX : Message goes to /chats//messages via POST request.

Assistant responds:

React : The client-side stream handler receives the data, stores it in state with the useState hook, and rerenders the conversation component every time new data comes in.

HTMX : The data gets appended into the , because the hx-swap property is set to beforeend. Once the message stops streaming, a small sse-close handler reloads the message so the markup can be applied server-side.

User reloads the page:

React : The HTML of the page reloads, followed by the React JavaScript bundle, followed by the chat data.

HTMX : The HTML contains all the data, and the page loads in one round-trip.

Bundle size

Let’s load a page and compare the total amount of data exchanged by the React and HTMX apps.

HTMX<br>React

HTML page<br>4.4kB<br>HTML page<br>0.6kB

htmx.min.js<br>17.2kB<br>index.js<br>390kB

sse.js<br>2.9kB<br>json API data<br>3.1kB

style.css<br>0.4kB<br>style.css<br>0.4kB

total<br>24.9kB<br>total<br>394.1kB

So the React app is over 15 times bigger on the client than the HTMX app! Why is the bundle so large? Let’s take a look with rollup-plugin-visualizer:

A large amount of space here is React itself (like react-dom-client.production.js). That means there isn’t much we can do to fix it; having a client use React at all requires sending 4 times more data to the user than our entire HTMX app. This makes sense in general and would be worth it if we needed all of React’s power, but we don’t seem to need it even in this fairly interactive app.

React Router uses another 31kb to reimplement native browser behavior, which we get for free in the HTMX app.

Also note that we are...

react htmx data page client user

Related Articles