HTMX: Boosted Navigation, OOB Swaps & Backend Patterns
hx-boost: Progressive Enhancement
<!-- hx-boost converts normal <a> links and <form> submissions inside
this container into AJAX requests that update just the body,
while keeping standard href/action attributes -- degrades
gracefully to normal navigation if JS fails to load -->
<div hx-boost="true">
<a href="/about">About</a>
<a href="/contact">Contact</a>
</div>
<!-- hx-push-url keeps the URL bar and browser history in sync with
an AJAX-driven navigation -- back/forward and refresh work
correctly, as if a real page load happened -->
<a hx-get="/products/42" hx-push-url="true" hx-target="#content">
View Product
</a>Out-of-Band Swaps
<!-- Server response for "Add to cart": updates the main target
(the button becomes a confirmation) AND, in the same response,
updates the cart badge count ELSEWHERE on the page via a
matching id + hx-swap-oob -->
<button hx-post="/cart/add" hx-target="this" hx-swap="outerHTML">
Add to cart
</button>
<!-- Server response HTML: -->
<!--
<button>Added ✓</button>
<span id="cart-count" hx-swap-oob="true">3</span>
-->
<!-- HTMX swaps the button normally, AND finds the existing
#cart-count on the page and swaps it too -- one round-trip,
two page locations updated -->Server-Side: Detecting HTMX Requests
// Express example -- one route serves BOTH a full page (direct
// navigation) and just the fragment (HTMX-triggered update) by
// checking the HX-Request header HTMX adds automatically
app.get('/messages', async (req, res) => {
const messages = await getMessages();
if (req.headers['hx-request']) {
// Just the fragment -- reuses the same partial template
res.render('partials/message-list', { messages });
} else {
// Full page, direct browser navigation
res.render('pages/messages', { messages });
}
});
// Validation errors: re-render the same form partial with inline
// errors instead of a separate JSON error-handling path
app.post('/messages', async (req, res) => {
const errors = validate(req.body);
if (errors.length) {
return res.status(422).render('partials/message-form', { errors, values: req.body });
}
const message = await createMessage(req.body);
res.render('partials/message-item', { message });
});Extensions & Hypermedia-Driven Apps
HTMX's core stays small and focused on request/swap behavior -- SSE, WebSockets, and other capabilities ship as opt-in extensions.
"Hypermedia-Driven Applications": the server owns both application state AND its HTML representation -- the client just requests and swaps fragments, avoiding duplicated client/server rendering logic.
Because the contract is just "return HTML," any server-side templating stack (Django, Rails, Laravel, Express) can serve HTMX requests -- no dedicated JSON API layer required.
Best fit: CRUD-heavy, mostly-server-driven apps. A framework with richer client-side state (React, etc.) may still fit better for deeply stateful or offline-capable client interactions.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free