
Replace Your Entire React App with 20 Lines of HTMX: A Step-by-Step Guide
Table Of Content
- The JavaScript Framework Fatigue Is Real 😫
- Enter HTMX: Hypermedia on Steroids ✨
- HTMX vs. React: Key Differences at a Glance
- The Challenge: A Simple Todo App 📝
- The Paradigm Shift: What's Really Happening Here? 🤯
- Why This Matters: The Real-World Benefits 📊
- Making the Switch: A Practical Migration Path 🛣️
- When to Stick with React ⚠️
- Conclusion: Less JavaScript, More Hypermedia 🌟
- References
Ever feel like you're drowning in JavaScript frameworks? Like you're spending more time wrestling with state management, component lifecycles, and build configurations than actually solving problems? You're not alone.
I hit this wall last month while rebuilding our team's dashboard app. What started as a "simple React project" had ballooned into a 200MB node_modules directory and enough boilerplate to make my eyes glaze over. There had to be a better way.
The JavaScript Framework Fatigue Is Real 😫
As web developers, we've been conditioned to believe that complex problems require complex solutions. Need a dynamic UI? Reach for React. Need state management? Add Redux. Need routing? Toss in React Router.
Before you know it, your "simple" web app has a dependency tree deeper than the Mariana Trench and a bundle size that would make dial-up users weep. This complexity is a common pain point leading developers to seek a React alternative.
Don't get me wrong – React is an incredible tool that revolutionized web development. But for many use cases, we're using a sledgehammer to drive in a thumbtack.
What if there was a way to achieve the same dynamic, interactive UIs with dramatically less code? If you're feeling bored out or burned out from JavaScript complexity, there's a better approach I've been experimenting with.
Enter HTMX: Hypermedia on Steroids ✨
HTMX is a small (~14KB min.gz), dependency-free JavaScript library that allows you to access modern browser features directly from HTML. Instead of bringing data to your JavaScript and then rendering HTML, HTMX lets you request HTML fragments from your server and inject them directly into your page. While other libraries like Alpine.js also aim to enhance HTML, HTMX focuses specifically on simplifying server interactions.
As Carson Gross, the creator of HTMX puts it:
HTMX gives you access to AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext.
This approach aligns perfectly with how the web was originally designed to work – as a hypermedia system where servers send HTML and browsers render it. It leverages server-rendered HTML benefits for modern interactivity.
HTMX vs. React: Key Differences at a Glance
Before diving into an example, let's look at a high-level HTMX comparison with React:
Feature | HTMX | React |
---|---|---|
Core Concept | Extends HTML with attributes | JavaScript library for building UIs |
Primary Unit | HTML attributes (hx-*) | Reusable Components |
Interactivity | Server sends HTML fragments | Client-side rendering via Virtual DOM |
State Management | Primarily server-side | Primarily client-side (useState, Context, Redux) |
Bundle Size | Very small (~14KB min.gz) | Larger (~40KB + ReactDOM ~120KB + extras) |
Complexity | Lower; less JS, no build step required | Higher; requires JS, JSX, build tools, state mgmt |
Learning Curve | Easier for HTML/backend devs | Steeper; requires JS/React ecosystem knowledge |
Use Case Sweet Spot | Enhancing server-rendered apps, simple UI | Complex SPAs, rich client-side interactions |
JavaScript Req. | Minimal (library only) | Significant |
The Challenge: A Simple Todo App 📝
To demonstrate the power of HTMX, I built the quintessential web developer example: a todo list application. I implemented the same functionality twice – once with React and once with HTMX – and the results honestly shocked me.
Our app has three core features:
1.Display a list of todos
2.Add a new todo
3.Delete a todo
The React Implementation 🔄
Here's our minimal React implementation:
(Note: This is example code, for a full working demonstration visit GITHUB)
import React, { useState, useEffect } from 'react';
// import axios from 'axios'; // Removed for simplicity in example
function App() {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState('');
// Simulate fetching todos from an API
useEffect(() => {
// In a real app, this would be an actual API call
const initialTodos = [
{ id: 1, text: 'Learn React' },
{ id: 2, text: 'Build something cool' }
];
setTodos(initialTodos);
}, []);
const handleSubmit = (e) => {
e.preventDefault();
if (!newTodo.trim()) return;
// In a real app, this would be a POST request
const todo = {
id: Date.now(), // Simple ID generation for example
text: newTodo
};
setTodos([...todos, todo]);
setNewTodo('');
};
const handleDelete = (id) => {
// In a real app, this would be a DELETE request
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div>
<h1>React Todo App</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="Add a new todo"
/>
<button type="submit">Add</button>
</form>
<div>
{todos.map(todo => (
<div key={todo.id} className="todo-item"> {/* Added basic class */}
<span>{todo.text}</span>
<button onClick={() => handleDelete(todo.id)}>Delete</button>
</div>
))}
</div>
</div>
);
}
export default App;
This is already a simplified version, but to make it work properly in a real project, we still need:
•React and ReactDOM libraries
•A build system (Webpack, Babel, Vite, etc.)
•State management for todos and form input (handled here with useState)
•Event handlers for form submission and deletion
I spent three hours debugging a weird state update issue last week that turned out to be a React 18 concurrent rendering quirk. No wonder developers are looking for ways to simplify web development and unlock 5X productivity with simpler approaches.
The HTMX Alternative: ~20 Lines That Change Everything 🚀
Now, let's implement the same functionality with HTMX. Here's the entire HTML for our application:
(Note: This is example code, for a full working demonstration visit GITHUB)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX Todo App</title>
<script src="https://unpkg.com/[email protected]"></script>
<!-- Basic CSS styles omitted for brevity -->
<style>
.todo-item { margin-bottom: 5px; }
/* Add other styles as needed */
</style>
</head>
<body>
<h1>HTMX Todo App</h1>
<!-- Form to add new todos -->
<form hx-post="/todos" hx-target="#todo-list" hx-swap="beforeend" hx-on::after-request="this.reset()">
<input type="text" name="todo" placeholder="Add a new todo" required>
<button type="submit">Add</button>
</form>
<!-- Container for the list of todos -->
<div id="todo-list">
<!-- Initial todos rendered by the server -->
<div class="todo-item">
<span>Learn React</span>
<button hx-delete="/todos/1" hx-target="closest .todo-item" hx-swap="outerHTML">Delete</button>
</div>
<div class="todo-item">
<span>Build something cool</span>
<button hx-delete="/todos/2" hx-target="closest .todo-item" hx-swap="outerHTML">Delete</button>
</div>
<!-- New todos will be appended here by HTMX -->
</div>
</body>
</html>
That's it. Roughly 20 lines of meaningful HTML (excluding boilerplate and basic styles). No JavaScript required beyond including the lightweight JavaScript library HTMX itself.
I had to double-check that I wasn't missing something when I first got this working. It seemed too simple.
Let's break down what's happening:
1.We include the HTMX library (a tiny ~14KB).
2.Our form has HTMX attributes:
-
hx-post="/todos": Send a POST request to /todos when submitted.
-
hx-target="#todo-list": Update the element with ID todo-list.
-
hx-swap="beforeend": Add the response content at the end of the target element.
-
hx-on::after-request="this.reset()": Reset the form after the request completes (a nice UX touch).
3.Each delete button has:
-
hx-delete="/todos/1": Send a DELETE request to /todos/1 (or the appropriate ID) when clicked.
-
hx-target="closest .todo-item": Target the closest ancestor element with the class todo-item.
-
hx-swap="outerHTML": Replace the entire target element (the todo item div) with the response (which should be empty on successful delete).
The server-side code (e.g., using Express in Node.js) needs to handle these /todos POST and /todos/:id DELETE requests, performing the database operations and returning the appropriate HTML fragments.
(Server-side code example omitted for brevity, but crucial for HTMX functionality)
The Paradigm Shift: What's Really Happening Here? 🤯
The HTMX approach represents a fundamental shift compared to typical SPAs:
•Server-Side HTML Generation: Instead of sending JSON and rendering it client-side, the server sends HTML fragments ready for the DOM.
•Declarative Interactivity: Behavior is declared directly in HTML attributes, not imperative JavaScript event handlers.
•Hypermedia-Driven Application: The server sends UI controls (hypermedia) along with data.
As Tom MacWright notes in his essay "If not SPAs, What?":
The cost of this approach [SPAs] is enormous, and the benefits are largely illusory... There's a weight to the complexity we've built up.
This approach reminds me of Einstein's philosophy: "Everything should be made as simple as possible, but no simpler."
Why This Matters: The Real-World Benefits 📊
This simpler approach yields tangible benefits:
•Dramatically Reduced Complexity: No complex state management libraries, component lifecycles, or build systems needed for many tasks. I deleted over 200 files from one project after converting parts to HTMX.
•Smaller Bundle Sizes: HTMX (~14KB) vs. React (~40KB) + ReactDOM (~120KB) + potentially Redux, Router, etc. Our dashboard's main bundle went from 2.3MB to 76KB after significant refactoring.
•Progressive Enhancement: Degrades gracefully if JavaScript fails. Basic form submissions might still work.
•Improved Performance: Less client-side JS often means faster loads and better runtime performance. Our dashboard's initial load improved from 3.2s to 780ms.
According to web performance research by the HTTP Archive, the median website ships over 400KB of JavaScript. HTMX offers a path to drastically reduce this while maintaining rich interactivity.
Making the Switch: A Practical Migration Path 🛣️
Want to migrate React applications to HTMX incrementally?
1.Start with New Features: Implement new, simpler interactive features using HTMX within your existing app.
2 .Identify Simple Components: Convert React components that mainly render data with simple interactions (like data tables or status indicators).
3.Convert Form Submissions First: Forms are often prime candidates for HTMX simplification.
Breaking the migration into small chunks makes the process manageable.
This approach allows you to learn the new paradigm faster by applying it incrementally. Following the 8 Rules of Getting Things Done, I found that breaking the migration into small, manageable chunks made the process much more efficient.
When to Stick with React ⚠️
HTMX isn't a silver bullet. React (or similar frameworks like Vue, Svelte, Angular) remains a better choice when:
• Building highly interactive applications with complex, real-time client-side state (e.g., collaborative design tools, complex games, rich text editors). React's component model and state management excel here.
• Needing robust offline capabilities where significant logic must reside client-side.
• Developing mobile applications with React Native, leveraging the shared ecosystem.
• Working with large, established teams already deeply invested and proficient in the React ecosystem.
Choose the right tool for the job. As a transformative leader, recognize where each technology shines.
Conclusion: Less JavaScript, More Hypermedia 🌟
The web began as a hypermedia system. Complex JavaScript frameworks, while powerful, often add significant overhead. HTMX offers a compelling return to simplicity, leveraging server-rendered HTML benefits for modern interactivity.
It lets us build rich applications with less code and complexity, potentially boosting focus and creativity by reducing cognitive load.
[!As Jeremy Keith argues in "Resilient Web Design":] "The history of web design has been a gradual process of adding power to the web browser... But that doesn't mean that the old-fashioned approach of generating HTML on the server is no longer valid."
By embracing HTMX where appropriate, you align with the web's original principles while leveraging modern capabilities. This approach can help avoid wasted time wrestling with framework complexities.
So next time you reach for React for a relatively simple interactive feature, ask yourself: Could this be an HTMX comparison opportunity? Could I simplify web development here with 20 lines of HTMX instead? Your future self (and your users) might thank you.
References
•How Did REST Come To Mean The Opposite of REST? by Carson Gross
•If not SPAs, What? by Tom MacWright
•HTTP Archive's Web Almanac: JavaScript
•Resilient Web Design by Jeremy Keith