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.

Create a free account to save this page, take notes and track your progress. Sign in

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.

Boosted linksTry it yourself
<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).

Push the URL into historyTry it yourself
<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?

  1. The full page, because the browser requests it without HX-Request
  2. The same fragment htmx received
  3. A redirect to the home page
  4. Nothing: 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.

Was this page helpful?