HTMX and the Return of Server-Rendered HTML
How htmx brings partial-page updates to hypermedia apps — no client-side state, no bundle, no build step. Core attributes, server patterns, and when it beats an SPA.
Published on • August 10, 2026
AI Assistant

The classic web model has three properties that SPAs accidentally discarded: every page is a resource with a URL (back button and bookmarks work for free), the server holds all state (no client cache to invalidate), and core interactions need zero client JavaScript. The historical tradeoff was UX — full-page reloads feel slow. HTMX solves exactly that by bringing partial page updates to hypermedia: instead of replacing the whole page, it swaps only the relevant DOM element. The architecture stays hypermedia-driven, but the UX feels like an SPA.
In this post, you will learn the core hx-* attributes, how the server returns HTML fragments instead of JSON, the patterns that make HTMX apps production-ready, and when to keep React instead.
The mental model shift
SPAs teach you to think in terms of data: “I need the latest comments as JSON, then I’ll map them into <li> elements.” HTMX teaches you to think in terms of representations: “I need the comments section rendered as HTML, and I’ll swap it into the DOM.” Your server becomes a hypermedia engine — it responds to requests with pre-rendered HTML fragments that are ready to display.
HTMX is a dependency-free library of about 14KB gzipped that extends HTML with attributes for making AJAX requests, handling SSE, WebSockets, and CSS transitions:
<script src="https://unpkg.com/htmx.org@2.0.0"></script>
<button hx-get="/api/greeting" hx-target="#result" hx-swap="innerHTML">
Say Hello
</button>
<div id="result"></div>
Core attributes
Six attributes cover most of what you’ll write:
hx-get/hx-post/hx-put/hx-delete— the HTTP verb and endpoint.hx-trigger— what triggers the request (default: click for buttons, change for inputs).hx-target— which element receives the response (default: the element itself).hx-swap— how to swap:innerHTML,outerHTML,beforeend, with optionalswap:500msfor smooth transitions.hx-indicator— an element to show while the request is in flight.hx-boost— upgrade all links/forms on an element to AJAX, turning a multi-page app into an SPA-like experience.
<div hx-get="/contacts/1/edit" hx-trigger="click" hx-swap="outerHTML">
<p>Name: Alice Johnson</p>
Click to edit
</div>
Clicking that div fetches the edit form from the server and swaps it in — click-to-edit with no onClick, no setState, no useEffect.
The server renders fragments
The server is the single source of truth. For an HTMX request, return just the fragment; for a full page load, return the complete layout. HTMX sets the HX-Request: true header on every request so the same route can serve both:
# FastAPI
from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse
app = FastAPI()
todos: list[str] = []
@app.get("/", response_class=HTMLResponse)
def index():
items = "".join(f"<li>{t}</li>" for t in todos)
return f"""<!doctype html>
<html><body>
<ul id="todos">{items}</ul>
<form hx-post="/todos" hx-target="#todos" hx-swap="beforeend">
<input name="title" required />
<button type="submit">Add</button>
</form>
</body></html>"""
@app.post("/todos", response_class=HTMLResponse)
def create(title: str = Form()):
todos.append(title)
return f"<li>{title}</li>" # just the fragment
The form’s hx-post="/todos" submits via AJAX, hx-target="#todos" points at the list, and hx-swap="beforeend" appends the returned <li>. No build step, no npm install, no JSON round-trip. The same pattern works in Django (request.htmx), Rails, Laravel, Express, Go templates — any framework that renders HTML.
Patterns that make HTMX feel modern
Active search with debounce
Search-as-you-type is a classic SPA pattern. With HTMX it’s three attributes — delay:300ms debounces so the server only receives a request when the user pauses:
<input type="search"
name="q"
hx-get="/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#results"
hx-indicator="#spinner"
placeholder="Search..." />
<div id="results"></div>
<span id="spinner" class="htmx-indicator">Loading…</span>
Infinite scroll
Each page of results renders the next page’s trigger. When the “Load More” button enters the viewport (revealed trigger), it fetches and replaces itself with the next page’s items and button:
<div hx-get="/items?page=2" hx-trigger="revealed" hx-swap="outerHTML">
<!-- items… -->
<button>Load More</button>
</div>
Full-page vs. partial detection
Check the HX-Request header to render a fragment for HTMX and a full page for direct navigation — so deep links, refresh, and SEO all work:
def comment_list(request):
comments = Comment.objects.all()
template = "_comment_list.html" if request.htmx else "comments.html"
return render(request, template, {"comments": comments})
When HTMX is the right call — and when it isn’t
HTMX genuinely shines for: content-heavy apps, CRUD dashboards, admin panels, and anything where server-side templates are already the model. Teams at every scale are shipping these — 48,000+ GitHub stars, 765,000+ monthly npm downloads, and htmx 2.0 standardized the API in 2024.
Keep a JavaScript framework for: real-time collaborative editing, complex data visualizations, offline-first apps, or rich client state machines. The pragmatic play is hybrid — keep React islands for the genuinely interactive widgets and let HTMX handle the page-level navigation.
Putting It All Together
A complete example: the FastAPI todo app above — one Python file and one HTML file, no bundler. Add active search with hx-trigger="keyup changed delay:300ms", wire hx-boost on <body> to get SPA-style navigation, and drop in a loading indicator. You’ve shipped a fully interactive app whose total client footprint is 14KB of htmx.
Conclusion & Next Steps
You now understand the hypermedia model, the core hx-* attributes, how the server serves fragments and full pages from the same route, and when HTMX beats an SPA. Next steps: build a CRUD app where every mutation swaps a fragment, try hx-boost on an existing server-rendered site, and read the Hypermedia Systems book that lays out the architecture behind it.
References / Sources
- htmx documentation and examples. https://htmx.org/docs
- HTMX server-side examples. https://htmx.org/server-examples/
- Hypermedia Systems — the book by htmx’s creator on hypermedia-driven applications. https://hypermedia.systems/
- MDN — Server-sent events with htmx. https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events