Using htmx hx-boost for SPA-like Navigation Without a Client Router
Learn how htmx’s hx-boost attribute turns regular links and forms into AJAX navigations that update the URL and swap server‑rendered fragments, giving an SPA feel while keeping the server as the single source of truth.
03 Feb 2026, 21:16 UTC

The problem: full‑page reloads feel slow and disruptive
When a user clicks a link or submits a form, the browser discards the current page, requests a new HTML document, and then paints the whole thing again. Even if only a small section changes, the user sees a flash, loses scroll position, and waits for assets that haven’t actually changed. For many internal tools or content‑heavy sites this overhead adds up.
Thesis: hx‑boost gives you the feel of a single‑page app while keeping the server as the single source of truth
htmx’s hx-boost attribute turns ordinary anchors and forms into AJAX requests. By default it swaps the response into document.body, updates the address bar via the History API, and adds an HX-Boosted header so the server can decide whether to return a full page or just a fragment. The result is navigation that feels instant, yet no client‑side router or state management library is required.
How hx‑boost works under the hood
- When an element has
hx-boost(or is inside a boosted parent), htmx intercepts the click/submit. - It issues an
XMLHttpRequest(or fetch) with the headerHX-Boosted: true. - By default the target is
bodyand the swap mode isinnerHTML, so the response replaces the current page’s body. - htmx pushes a new state onto the history stack, updating the URL without a reload.
Controlling where the fragment goes
Sometimes you want only a sidebar or a modal to update. You can override the defaults on the boosted element itself:
<a href="/articles/42" hx-boost hx-target="#sidebar" hx-swap="outerHTML"
>Article 42</a>
Here the response replaces the whole #sidebar element, leaving the rest of the page untouched.
History and scroll restoration
htmx stores a snapshot of the DOM before each boosted navigation. When the user presses the back button, it restores that snapshot and attempts to scroll to the previous position. The reliability of this behavior depends on the server returning stable markup for the boosted fragment across requests. If the fragment changes structure (e.g., adds or removes a wrapper), the restored scroll position may drift.
Worked example: boosting a blog index
Imagine a simple blog where the index page lists article titles. Clicking a title should show the article content without a full reload.
Server side (pseudo‑code)
# Route: GET /articles/:id
if request.headers.get('HX-Boosted') == 'true':
# Return only the article fragment
return render_template('article_fragment.html', article=article)
else:
# Full page for direct navigation or SEO
return render_template('article_full.html', article=article)
Client side
<!-- index.html -->
<ul id="article-list">
{% for article in articles %}
{{ article.title }}
{% endfor %}
</ul>
When a link is clicked:
- htmx sends an AJAX request with
HX-Boosted: true. - The server detects the header and returns
article_fragment.html(just the article’s heading, body, and maybe a back link). - htmx swaps the response into
document.body, replacing the list with the article. - The URL in the address bar updates to
/articles/42without a page reload.
To verify, open DevTools → Network, click a boosted link, and confirm:
- The request header includes
HX-Boosted: true. - The response body contains only the fragment, not a full
<html>wrapper. - The URL changes and the page does not flicker.
- Add a test route that asserts the presence of the
HX-Boostedheader and returns a 400 if it’s missing but a full page is detected. - During development, render a comment like
<!-- BOOSTED FRAGMENT START -->in fragments and watch for it in the Network tab. - Use feature flags or a middleware that logs when a boosted request returns a document with both
<html>and<body>tags.
Trade‑off: the server must support partial rendering
The biggest engineering risk is that a boosted route accidentally returns a full document or an empty body. In that case htmx will still swap the response into body, leading to duplicated content (e.g., two <html> tags) or a blank screen, and there is no client‑side error to signal the problem. Mitigation strategies:
Actionable closing
Start small: pick a single navigation link or a form that currently causes a full reload, add hx-boost, and verify the three checks above. Once that works, expand to other links, remembering to opt‑out with hx-boost="false" for actions that truly need a traditional navigation (file downloads, external redirects, or POSTs that must reset state). By treating the server as the source of truth and letting htmx handle the swap, you get SPA‑like responsiveness without the complexity of a client‑side router.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.