Back to blog
Jul 13, 2024
7 min read

LOB: HTMX is better than React

this is definitely not clickbait

react_htmx

Locality of Behaviour: How HTMX dunked on React

It seems like every new project wants you to jump on React. And why not? React is fast, the community is massive, and the tooling is… well, it’s there. But sometimes it feels like you’re using a powerdrill when all you need is a screwdriver: HTMX. Why? Because HTMX understands the concept of locality of behavior—something that React seems to have thrown out the window.

What Is Locality of Behavior?

Locality of behavior means keeping the behavior (e.g., JavaScript interactions) close to the markup they affect. In plain terms, it’s about having your code do what it says on the tin—no more, no less, and right where you expect it. React, for all its declarative elegance, has this habit of hiding behavior inside a labyrinth of component trees, state management hell, and useEffect hooks from the seventh circle.

HTMX, on the other hand, keeps it simple. It lets you declare your interactions right in the HTML where they belong. Want a button to do something? Just add an attribute. No state management, no complex prop drilling, and definitely no mental breakdowns from trying to untangle hooks.

React Approach

React likes to take over your whole DOM with components. A simple task like loading data from the server becomes a multi-file, multi-hook dance. Here’s a React component that fetches some data when you click a button:

// ButtonFetch.js
import React, { useState } from 'react';

const ButtonFetch = () => {
  const [data, setData] = useState(null);

  const handleClick = async () => {
    const response = await fetch('/api/data');
    const result = await response.json();
    setData(result);
  };

  return (
    <div>
      <button onClick={handleClick}>Fetch Data</button>
      {data && <p>{data.message}</p>}
    </div>
  );
};

export default ButtonFetch;

HTMX Approach

HTMX doesn’t need the whole song and dance to fetch data. Just write what you mean in your HTML, and HTMX will handle it with no additional JavaScript required:

<!-- index.html -->
<button 
  hx-get="/api/data" 
  hx-target="#result" 
  hx-swap="innerHTML">
  Fetch Data
</button>
<p id="result"></p>

Boom. One line of HTML and you’re done. The HTMX approach is readable, declarative, and most importantly, it’s local. The behavior of the button is defined right there, where you expect it. No extra context needed, no weird state to manage, just plain HTML doing its job.

Why HTMX Is Better

  1. Less JavaScript Overhead: React loves JavaScript—like, a lot. It’s great until you realize your 100KB React app is doing the same job as a 20KB vanilla JavaScript solution. HTMX, however, keeps it lean by handling dynamic behaviors directly in HTML.

  2. Better Performance: React’s reconciliation and virtual DOM diffing are impressive feats of engineering, but they add complexity and performance overhead. HTMX operates directly with the real DOM, making it faster for simple interactions.

  3. No Build Tools: React projects typically need Webpack, Babel, Vite, or other build tools to get off the ground. HTMX just works. Drop it into any HTML page, and happy days. No need for a massive build pipeline to render a button.

  4. Locality of Behavior: Keeping the behavior close to the markup makes it easy to understand and maintain. No hunting for state mutations buried in a component tree or trying to unravel why a hook is re-running for the 14th time. With HTMX, you know exactly what’s happening and where.

Debugging Hell vs. Bliss

In React, debugging often feels like trying to solve a Rubik’s cube while blindfolded. You need to navigate through component trees, understand state management, and remember all the quirks of the virtual DOM. HTMX’s debugging story? Look at your HTML. That’s it. The interactions are right where you put them, and any issues are usually due to server-side responses, which are much easier to diagnose.

tldr;

React has its place. For complex SPAs with lots of client-side state, React might still be your best friend. But for many of those content-rich tools like forms and dashboards - HTMX is often the simpler, more maintainable, and ships faster. It’s time to get back to basics and embrace locality of behavior.

But wait, there’s more HTMX can be a great addition to Next.js, especially for cases where you want server-side rendering (SSR) but need more dynamic behavior without the overhead of React’s client-side state management.

  1. Simplify Interactions: For simple interactions that don’t need the full weight of React, HTMX can make your code more readable and maintainable.

  2. SSR and Dynamic Updates: Next.js is excellent for server-rendered pages, and HTMX can enhance this by allowing you to fetch and swap content dynamically without a full-page reload, making your SSR even more interactive.

  3. Reduce JavaScript Load: Using HTMX can cut down on JavaScript bundle sizes and client-side complexity by keeping things declarative and close to your HTML.

Setting Up HTMX in a Next.js App

Here’s a basic example to get you started with HTMX inside a Next.js project.

  1. Install HTMX: You can include HTMX by adding it directly to your Next.js _document.js file.

    npm install htmx.org
    
  2. Include HTMX in _document.js: This will ensure that HTMX is included on every page of your Next.js app.

    // pages/_document.js
    import { Html, Head, Main, NextScript } from 'next/document';
    
    export default function Document() {
      return (
        <Html>
          <Head>
            <script src="https://unpkg.com/htmx.org@1.9.2"></script>
          </Head>
          <body>
            <Main />
            <NextScript />
          </body>
        </Html>
      );
    }
    
  3. Use HTMX in Your Next.js Pages: Now you can use HTMX in any of your Next.js pages or components.

Here’s an example where we use HTMX to fetch some data when a button is clicked. The request is handled by a Next.js API route.

Button component using HTMX:

// pages/index.js
export default function Home() {
  return (
    <div>
      <h1>Next.js with HTMX</h1>
      <button 
        hx-get="/api/data" 
        hx-target="#result" 
        hx-swap="innerHTML">
        Fetch Data
      </button>
      <p id="result"></p>
    </div>
  );
}

Next.js API Route:

Create an API route that returns the data when the button is clicked.

// pages/api/data.js
export default function handler(req, res) {
  res.status(200).json({ message: 'Hello from HTMX and Next.js!' });
}

How It Works

  • When the button is clicked, HTMX makes an AJAX request to /api/data.
  • The response from the API route (a JSON message) is then inserted into the element with the id="result" using HTMX’s hx-swap="innerHTML" attribute.

Benefits of Combining HTMX with Next.js

  1. Server-Side Rendering with Dynamic Behavior: You get the performance and SEO benefits of SSR from Next.js combined with the client-side interactivity that HTMX provides, without all the client-side React boilerplate.

  2. Simpler Data Fetching: No need for React state, hooks, or context just to get some data onto your page. HTMX handles this elegantly with simple attributes.

  3. Reduced Complexity: HTMX keeps your interactions localized, making your code easier to understand and debug.

When to Use HTMX Inside Next.js

  • Static Sites with Dynamic Elements: If your site is mostly static but has a few areas needing interactivity (idk, a blog?), HTMX is perfect.
  • Forms and Small Interactions: For form submissions, button clicks, and other small interactive elements, HTMX can replace complex React forms and state management.
  • Prototyping: HTMX allows for rapid prototyping within your Next.js app without setting up complex client-side logic.