Skip to main content

๐ŸŒ Handling HTMX Browser History and Navigation

ยท 4 min read
Jason Hollifield
Founder @ Hollplex

How I handled using HTMX and browser refreshes

In modern web development, creating responsive and interactive applications without the complexity of a full-blown single-page application (SPA) framework has become more accessible, thanks to libraries like HTMX. HTMX allows developers to update parts of a web page with AJAX requests, making it feel like an SPA without the heavy lifting. However, this approach introduces a significant challenge: managing browser history and navigation.

โ— The Problemโ€‹

The primary issue arises when HTMX makes AJAX requests to update the page dynamically. While this process enhances the user experience by loading content faster and reducing server load, it disrupts the traditional browser history and navigation. Users navigating through the dynamically loaded content might find that the back button doesn't work, or refreshing the page could result in a different state than anticipated. This can lead to confusion and a frustrating user experience, as the seamless navigation expected from modern web applications is broken.

๐Ÿงฉ The Challenge with Browser Historyโ€‹

When you interact with an HTMX-powered website, your actions don't automatically update the browser's history. This is because AJAX requests modify the current page's content without informing the browser that a navigational change has occurred. Therefore, when a user attempts to use the back or forward button, the browser doesn't have the necessary information to restore the page to its previous state.

๐Ÿ’ก My Solutionโ€‹

To fix this issue, I took an approach involving watching for the HX-Request header in application web routes and using a boolean flag in the template rendering process. Here's how it works:

๐Ÿ“ฅ Identifying HTMX Requestsโ€‹

The server checks for the presence of the HX-Request header in incoming requests. This header is specific to HTMX and indicates that the request was made by the library for a partial page update. By detecting this header, the server can differentiate between an HTMX request and a standard browser request.

func about(w http.ResponseWriter, r *http.Request) {
data := TemplateData{
IsHTMX: r.Header.Get("HX-Request") == "true",
}


tmpl := template.Must(template.ParseFiles("templates/about.html", "templates/indexBefore.html", "templates/indexAfter.html"))
tmpl.Execute(w, data)
}

๐Ÿ–ผ๏ธ Conditional Page Renderingโ€‹

Based on whether the request is from HTMX, a boolean flag is passed to the template renderer. If the request is not from HTMX (indicating a full page load, such as a browser refresh or a link pasted into the address bar), the server prepends and appends the necessary HTML structure to the dynamic content. This ensures that the full page context is maintained for such requests, preserving the user experience across refreshes and direct navigation.

{{if not .IsHTMX}}
{{template "indexBefore.html"}}
{{end}}
<div class="grid gap-4" id="688gsnjtere">
<h2 class="text-2xl font-bold">About This Blog</h2>
<p class="text-gray-500 dark:text-gray-400">
Over the years, I've worked on a variety of personal projects and built a multitude of things. I wanted a place
to document it all, so I decided to code my own blog site. This blog is a culmination of my experiences and
learnings. ๐Ÿง 
</p>
<p class="text-gray-500 dark:text-gray-400">
Here, you'll find articles on a wide range of topics including 3D printing, manufacturing, programming,
networking, automation, hacking, electronics, and more. Each post is a reflection of my journey in these fields,
and I hope they can provide some insight or inspiration to you. ๐Ÿ’ก
</p>
<p class="text-gray-500 dark:text-gray-400">
I believe in the power of sharing knowledge, and this blog is my way of contributing to the community. Whether
you're a fellow enthusiast or a curious learner, I hope you find something of value here. ๐ŸŽ‰
</p>
</div>
{{if not .IsHTMX}}
{{template "indexAfter.html"}}
{{end}}

๐ŸŽฏ HTMX hx-push-url Attributeโ€‹

In addition to the HX-Request header, HTMX provides a hx-push-url attribute that allows developers to update the browser's history when content is dynamically loaded. This attribute is used to update the browser's address bar so it reflects the current state of the page, enabling users to navigate back and forth to the previous state.

<a class="cursor-pointer text-sm font-medium hover:underline underline-offset-4" hx-get="/about" hx-push-url="/about" hx-trigger="click" hx-target="#mainContent" hx-swap="innerHTML">
About
</a>

๐ŸŽ‰ Conclusionโ€‹

Integrating HTMX into web projects offers significant advantages in terms of responsiveness and interactivity. However, managing browser history and navigation is a critical aspect that requires careful consideration. My approach isn't perfect, it requires the application to be easily prepended and appended to the dynamic content. It also requires maintaining of duplicate code in the templates. However, it provides a starting point for addressing the issue and can be further refined to suit specific project requirements. By implementing a solution that manages browser history and navigation, developers can ensure a seamless user experience while leveraging the benefits of HTMX.