DEV Community

Yawar Amin
Yawar Amin

Posted on • Edited on

Why hx-boost is actually the most important feature of htmx

SOME of you may know by now that I'm a fan of htmx and its hypermedia-driven website/application design philosophy. When people first learn about htmx, they are often captivated by the idea of hx-get, hx-post, hx-delete, etc. attributes being able to fire off HTTP requests with these methods from any tag on the page. I know I was!

But it turns out there's a really good reason why we shouldn't just fire off requests with any method from any tag on the page: graceful degradation. The idea that even if the JavaScript on the page does not work for whatever reason–turned off on the user's browser, not loaded yet due to big scripts, or maybe due to a slow connection–the user should still be able to interact with most (if not all) of the functionality on the page.

For example, if JavaScript doesn't load, we want users to be able to submit a sign-up form or click on a link to load a new page.

This is what the hx-boost attribute is for. As mentioned on that page:

This has the nice fallback that, if the user does not have javascript enabled, the site will continue to work.

But graceful degradation isn't just a way to make websites that work without JavaScript; it leads us towards a more hypermedia-oriented design.

hx-boost–the starting point

hx-boost is a widely misunderstand and, I feel, misused attribute. Its documentation seems to suggest that it should be applied on an entire section of the page to 'boost' all the links inside it:

<div hx-boost=true>
  <a href=/page1>Go To Page 1</a>
  <a href=/page2>Go To Page 2</a>
</div>
Enter fullscreen mode Exit fullscreen mode

And that 'boosting' would simply send the request to the given URL, swap out the entire <body> tag with the <body> tag from the response, and update the URL in the URL bar.

However, I think this is an incorrect way to use hx-boost. The biggest reason why is that applying htmx attributes and inheriting them into large sections of the page tends to lead to unpredictable behaviours and bugs. Also, it is missing the key point of htmx, which I feel is to do targeted updates to parts of the page, not the entire <body> of the page.

So why is hx-boost misused then? I believe it was intended as a shortcut to give people with classic multi-page 'apps' an easy way to make their apps 'feel' like SPAs without having to do the actual work to break down the pages into components. But I believe that this work is actually crucial to making an HDA–a Hypermedia Driven App–that has a great user experience.

Boosting only

The first decision we need to make is to eliminate or at least minimize the use of the hx-get, hx-post, etc. attributes and use boosting only unless we have a very good reason not to. Why? Remember–graceful degradation. The hx- + method attributes work as long as JavaScript and htmx are available, but become no-ops otherwise. Boosted elements gracefully degrade to their basic browser-provided functionality!

Boosted links

Links ie <a> tags do two things: load the resource from the given URL, and update the URL in the URL bar (+ pushing into the page's history stack, but we will treat both as the same thing in this article). Boosted links do the same thing but can additionally swap only parts of the current page, instead of the entire page. Eg:

<a href=/page1 hx-boost=true hx-target=#page>Go To Page 1</a>
Enter fullscreen mode Exit fullscreen mode

This will make htmx request /page1 and swap the response HTML into the element with ID page. Remember that the default swap style is innerHTML, so the content will be inside the #page element, which will be preserved.

But wait...how should the server know to respond with a fragment that is appropriate to be swapped inside #page? Well, htmx sends a request header HX-Request: true with every request. So the backend server can use a fairly simple heuristic to figure out that it should respond with a fragment:

  • The request has a header HX-Request
  • The request does not have a header HX-History-Restore-Request

If these two conditions are fulfilled, it can respond with a fragment. Otherwise, it can respond with a full page ie <!DOCTYPE html>... and so on.

Note: when the server does use the values of these headers to determine the response content, it must set the response header Vary: HX-Request, HX-History-Restore-Request. This is to ensure that the browser caches the response correctly.

Then, htmx will automatically update the URL in the URL bar for boosted links; you don't have to manage that explicitly. Basically, boosting works as closely as possible to a normal, non-JavaScript hyperlink.

Lastly and very crucially, boosted links work with not only right-click 'open in background tab', but also with Ctrl-click/Cmd-click which users have come to expect from normal links. This may be surprising but Ctrl-click/Cmd-click does not work with hx-get–it works only with regular and boosted links!

Boosted forms

The other thing that automatically 'just works' with boosting is HTML forms:

<form method=post action=/page1 hx-boost=true hx-target=#page>
  <input name=title>
  <input name=contents>
  <button type=submit>Save Page</button>
</form>
Enter fullscreen mode Exit fullscreen mode

When this form is submitted, htmx will automatically do the submission asynchronously using XHR and swap in the response. However, it will not update the URL in the URL bar automatically, because that's not how normal form submits work. Again, like most things in htmx, you can also override this (with the hx-push-url attribute).

Just like before, the server can figure out whether to respond with a fragment or a full page based on the request headers sent by htmx. For fragment responses, you just respond with an appropriate status code like 200 or 201 and the HTML fragment. For full page responses you respond with a 302 redirect as usual for the HTML form post-redirect-get pattern.

Accessibility

As many have pointed out, certain patterns that are possible with htmx are not great for accessibility (and for that matter, SEO). Eg, look at this: <div hx-get=/page1>Get Page1</div>.

Or this: <a hx-get=/page1>Get Page1</a>.

These are using htmx to essentially perform the function of <a href=/page1>Get Page1</a>. But the <a href=...>...</a> hyperlink is kind of well defined in HTML and many technologies, eg assistive technologies like screen readers, expect them to be structured in a certain way. Crucially, the href attribute needs to be present to allow assistive tech (and SEO crawlers) to understand the link. When we reinvent these patterns, these tools don't work as well.

Or look at this: <a hx-post=/page1>Save Page</a>. Or even: <a hx-delete=/page1>Delete Page</a>. I hope you can agree with me that the venerable anchor tag should not be repurposed to post or delete resources!

On top of that, using buttons for data loads and navigations takes away the standard web affordance of opening links in background tabs, which can be a huge productivity drain.

If we stick with boosted links and forms, we are making a commitment to adhere to established standards of web usability and accessibility.

Application design

Boosted links also have a big impact on the structure of your application. When most of the links will be shown in the URL bar at some point, you start carefully considering the set of URLs exposed by the app and the views that should be rendered when the user navigates to them. It becomes very important that URLs consistently render the same views on every visit. Eg,

  • /payment-methods should render a list of payment methods.
  • /payment-methods/new should render an 'Add payment method' form on the page.
  • /payment-methods/$id should render the payment method with the given ID,
  • and so on.

The ability to copy and paste, and bookmark, and send URLs to refer to specific parts of your app is a superpower that should not be underestimated. It's the difference between being able to point your user directly at the entry form to add a payment method and having to ask them to click around in the app looking for buttons to press to find the correct page or dialog.

Of course, this is nothing new in web application design; but a shocking number of SPAs nowadays don't follow these basic principles.

The cult of hx-boost

I don't believe in being dogmatic about this approach. There can be great reasons to use the hx- + method attributes. Eg, I've found it useful to do something like <tr hx-get=/foo hx-target=#detail-view style=cursor:pointer> in tables where I want the user to be able to easily select a row in a table and load some details. If JavaScript or htmx are not working, we can just have a <td><a href=/foo>Foo</a></td> cell in each row to gracefully provide only a slightly less convenient version of the user experience.

A lot is possible in htmx, but a little goes a long way. Think about these tenets:

  • If you are requesting something, try using a boosted link (or a form with method=get for a more complex query)
  • If you are changing something, try using a boosted form

These simple building blocks can provide a solid, accessible foundation for your app.

Top comments (0)