HTMX and the Return of Server-Rendered HTML


For the past decade, the default architecture for web applications has been a JavaScript SPA (single-page application) consuming a JSON API. React, Vue, Angular — pick your framework, the pattern is the same. The server sends data, the client renders it.

HTMX flips this pattern on its head. The server sends HTML, and the browser swaps it into the page. No JavaScript build step, no virtual DOM, no state management library. Just HTML attributes that tell the browser what to fetch and where to put it.

How HTMX Works

HTMX extends HTML with attributes that trigger HTTP requests and process HTML responses. The core idea is simple:

<button hx-get="/api/users" hx-target="#user-list" hx-swap="innerHTML">
  Load Users
</button>

<div id="user-list">
  <!-- User list HTML will be inserted here -->
</div>

Clicking the button sends a GET request to /api/users. The server returns HTML (not JSON), and HTMX replaces the contents of #user-list with the response. No JavaScript code written.

This pattern extends to forms, search, pagination, infinite scroll, and most other interactive patterns:

<input
  type="search"
  name="q"
  hx-get="/search"
  hx-trigger="keyup changed delay:300ms"
  hx-target="#results"
  placeholder="Search..."
/>

That’s a live search input. As the user types, HTMX debounces the input and fetches search results as HTML from the server. Three attributes replace what would typically be a React component with useState, useEffect, a debounce utility, and an API call.

The Architecture Shift

With HTMX, your server is responsible for rendering HTML for every interaction, not just the initial page load. This means:

Your backend framework is your UI framework. Django templates, Rails ERB, Laravel Blade, Go templates — whatever your backend already uses for rendering HTML becomes your primary tool for building interactive interfaces.

No JSON serialisation layer. You don’t need to design API responses, handle serialisation, or maintain a separate API contract for your frontend. The server renders the HTML it wants the user to see.

No client-side state management. The server is the single source of truth. There’s no Redux store, no Zustand, no context providers. The current state of the UI is whatever HTML the server last sent.

When HTMX Makes Sense

CRUD applications. Admin panels, dashboards, content management systems — applications that are primarily about creating, reading, updating, and deleting records. These are the bread and butter of web development, and HTMX handles them with minimal complexity.

Server-heavy architectures. If your team’s strength is backend development (Python, Ruby, Go, Java), HTMX lets you build interactive applications without splitting your team’s attention across frontend and backend concerns.

Performance-sensitive pages. HTMX’s library is about 14KB. Compare that to a React application that typically ships 150KB+ before your application code. For users on slow connections, this difference is significant.

When HTMX Doesn’t Make Sense

Highly interactive UIs. Drag-and-drop interfaces, real-time collaborative editing, complex data visualisation — these require the kind of fine-grained DOM manipulation that a client-side framework provides. HTMX operates at the level of HTML fragments, not individual DOM nodes.

Offline-capable applications. HTMX requires a server connection for every interaction. If your application needs to work offline or on unreliable networks, a client-side framework with local storage is the better choice.

Complex client-side state. If your UI has state that doesn’t correspond to anything on the server (e.g., which panels are expanded, drag positions, undo history), managing that state through server round-trips adds latency and complexity.

Practical Patterns

Active search with loading indicators:

<div hx-get="/search" hx-trigger="keyup changed delay:300ms from:#search"
     hx-indicator="#spinner">
  <input id="search" type="search" name="q" />
  <span id="spinner" class="htmx-indicator">Searching...</span>
  <div id="results"></div>
</div>

Inline editing:

<div hx-get="/contacts/1/edit" hx-trigger="click" hx-swap="outerHTML">
  <span>John Smith</span>
  <span>[email protected]</span>
</div>

Clicking the div fetches an edit form from the server. Submitting the form sends a PUT request and swaps the form back to the display view.

Delete with confirmation:

<button hx-delete="/contacts/1"
        hx-confirm="Are you sure?"
        hx-target="closest tr"
        hx-swap="outerHTML swap:500ms">
  Delete
</button>

The Ecosystem

HTMX works with every backend framework because it’s just HTTP and HTML. The backend communities have embraced it enthusiastically. Django has django-htmx, Rails has hotwire (similar philosophy), Go has several HTMX-focused libraries, and Laravel integrates naturally.

The HTMX community is also refreshingly practical. The documentation is excellent, the examples are real-world, and the discourse focuses on solving problems rather than debating abstractions.

My Take

HTMX isn’t going to replace React for complex web applications. But it’s revealed that a significant percentage of web applications aren’t complex — they were just built with complex tools. For those applications, HTMX offers a simpler, faster, more maintainable alternative.

If your next project is a CRUD application and your team knows a backend language well, try building it with HTMX before reaching for a JavaScript framework. You might be surprised how far you get.