htmx Decision Guide: hx-boost for Whole Pages vs. Targeted hx-get/hx-post Swaps
A decision guide for htmx: when hx-boost's full-body progressive enhancement is enough, when targeted hx-get/hx-post swaps pay off, and how to validate both with devtools.
20 Sept 2025, 17:54 UTC

When you add htmx to a server-rendered app, the first real engineering decision is not which attribute to type — it is which of two supported update models to build around. hx-boost turns ordinary links and forms into AJAX navigation that replaces the whole <body>. Targeted swaps (hx-get/hx-post with hx-target and hx-swap) let the server return only the fragment that changed. They solve different problems, and mixing them deliberately is usually the right answer.
Version note: attribute names, defaults, and history behavior are version-sensitive. Pin your htmx version and confirm the behaviors below against its bundled docs before shipping.
The decision and its constraints
Ask two questions per interaction:
- How much of the page actually changes? If the answer is "most of it" (navigating to a new view), boosting is cheap and correct. If the answer is "one list, one counter, one form result," a full-body swap wastes bandwidth and can disturb state in untouched regions.
- Can the server return partial HTML? Targeted swaps require endpoints that render a fragment instead of a full layout. If your backend only renders complete pages, hx-boost gets you progressive enhancement with zero new endpoints.
Options compared
| hx-boost | hx-get/hx-post + hx-target | |
|---|---|---|
| Markup cost | One attribute on a container or element | Several attributes per interactive element |
| Response payload | Full page body | Only the changed fragment |
| Server work | Reuse existing full-page routes | Needs fragment-rendering endpoints (or HX-Request branching) |
| No-JS fallback | Works natively — links and forms stay valid | Forms/links can still fall back if you keep real action/href attributes |
| History/URL | Pushes history entries automatically | No URL change unless you add hx-push-url |
| Risk to untouched regions | Entire body re-rendered; third-party widgets re-initialize | Only the target element is touched |
Trade-offs that bite in practice
Full-body swaps re-run everything. A boosted navigation replaces the body, so any JavaScript that initialized on page load (maps, charts, date pickers) must be idempotent or re-initialized after each swap. With targeted swaps, those regions never move.
Targeted swaps can inject the wrong thing. If your endpoint returns a full HTML document to a request that expects a fragment, htmx will happily insert a whole page inside your div. The standard fix is to check the HX-Request header server-side and render a partial template when it is present.
outerHTML replaces the trigger. With hx-swap="outerHTML", the element that fired the request is replaced by the response. If the response omits the htmx attributes or IDs the element had, subsequent interactions silently stop working. Make the server re-render those attributes.
Back-button behavior diverges. Boosted navigations push history automatically. A targeted swap does not change the URL, so after three fragment updates the back button leaves the page entirely instead of undoing one step. If the partial state should be addressable, add hx-push-url="true" (or a specific URL) and test the back button explicitly.
Scripts inside fragments. Content swapped in is processed by htmx automatically, but third-party <script> tags inside a fragment may not execute depending on configuration. If a fragment relies on embedded scripts, test that path rather than assuming it works.
Concrete implementation: a hybrid page
The common, supported pattern is to boost navigation-level links and use targeted swaps for interactive widgets. Here is a search page skeleton:
<body>
<nav hx-boost="true">
<a href="/dashboard">Dashboard</a>
<a href="/search">Search</a>
</nav>
<input type="search" name="q" placeholder="Search..."
hx-get="/search/results"
hx-trigger="keyup changed delay:300ms"
hx-target="#results"
hx-swap="innerHTML">
<ul id="results">
<!-- server returns only fragments here -->
</ul>
</body>
The nav links are ordinary <a> elements, so they work with JavaScript disabled; with htmx loaded they swap the body and push history. The search input fires a debounced GET (300 ms after the user stops typing) and replaces only the list's contents.
On the server, branch on the request header so the same URL can serve both cases:
# Pseudocode — adapt to your framework
if request.headers.get("HX-Request") == "true":
return render_partial("results_list.html", items=items)
return render_page("search.html", items=items)
Run this wherever your backend lives; it needs no special permissions beyond your normal app deployment. The risk to watch: if you forget the branch, the fragment endpoint returns a full document and your #results list ends up containing an entire nested page — visible immediately in the rendered DOM.
How to validate the choice
You can verify both behaviors in a few minutes with browser devtools; no invented metrics needed, just observation:
- Open the Network tab and click a boosted nav link. Confirm the response is a full HTML document and the URL in the address bar changed.
- Type in the search box. Confirm each request to
/search/resultsreturns only the fragment markup and that only#resultschanges in the Elements panel. - Disable JavaScript and click the nav links again. They should perform normal full-page navigations — that is the progressive-enhancement guarantee of hx-boost.
- Perform several targeted swaps, then press the back button. Without
hx-push-urlyou leave the page; with it, each swap should be a history entry. Pick the behavior your UX requires and test it.
Limitations
hx-boost does not make a multi-page app feel like a SPA for free: large pages with heavy third-party widgets can feel worse after boosting because everything re-initializes on every navigation. Conversely, targeted swaps multiply the number of endpoints and templates you maintain, and every one of them needs the HX-Request branch and correct attribute re-rendering. Start with hx-boost for navigation, add targeted swaps where measurements (or obvious payload waste) justify them, and re-check behavior whenever you upgrade htmx versions.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.