htmx Tutorial · Going Further · Lesson 22 of 30
hx-boost, hx-push-url and History
Make normal links and forms load without a full page refresh, update the address bar, and keep the Back button working.
hx-boost
hx-boost="true" on a container upgrades every normal link and form inside it. A click still requests the same
URL, but htmx fetches it in the background, swaps the new page's <body> into the current one and updates the
address bar. The browser doesn't reload CSS and JavaScript, so navigation feels faster.
<body hx-boost="true">
<nav><a href="/">Home</a> <a href="/blog">Blog</a> <a href="/about">About</a></nav>
...
</body>
The big advantage: it degrades gracefully. Without JavaScript the links still work, because they're real links.
In the example, hx-target, hx-select and hx-swap (inherited by the boosted links) narrow the swap from the
whole <body> to the <main> element, a common optimisation that keeps the header and navigation in place.
<nav hx-boost="true" hx-target="#page" hx-select="#page" hx-swap="outerHTML">
<a href="/home">Home</a> · <a href="/blog">Blog</a> · <a href="/about">About</a>
</nav>
<main id="page"><h3>Home</h3><p>Click the links: the page changes without a reload.</p></main>
<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
const page = (title, text) => `<!doctype html><html><head><title>${title}</title></head><body>
<nav>...</nav><main id="page"><h3>${title}</h3><p>${text}</p></main></body></html>`;
server.get("/home", () => page("Home", "Welcome back."));
server.get("/blog", () => page("Blog", "Latest post: Why hypermedia?"));
server.get("/about", () => page("About", "We teach web development."));
</script>
hx-push-url
For your own htmx requests, hx-push-url="true" puts the request URL in the address bar and adds a history entry, so
the URL can be shared and the Back button returns to the previous state. hx-push-url="/some/path" pushes a
different URL; hx-replace-url changes the URL without adding a history entry (good for search filters).
<div hx-target="#article" hx-push-url="true">
<button hx-get="/articles/1">Article 1</button>
<button hx-get="/articles/2">Article 2</button>
</div>
<article id="article">Pick an article. Watch the address bar in the log.</article>
<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
server.get("/articles/:id", (req) => `<h3>Article ${req.params.id}</h3><p>Shareable at /articles/${req.params.id}.</p>`);
</script>
History and the server
When the user goes Back, htmx restores a snapshot of the page it saved. If the snapshot is missing, or the user
reloads or opens a shared link, the browser loads the URL directly. So every URL you push must also return a
full page for a normal request (without the HX-Request header).
The previews on this site can't change their real address, so the log shows what the address bar would say.
Test yourself
A user reloads a page whose URL was pushed by htmx. What must the server return?
The full page, because the browser requests it without HX-RequestThe same fragment htmx receivedA redirect to the home pageNothing: htmx restores it from memory
Show answer
Answer: The full page, because the browser requests it without HX-Request
Pushed URLs must work as normal page loads; check the HX-Request header to decide between fragment and page.