HTMX History Management — Complete Guide with Examples
In this tutorial, you'll learn about HTMX history management. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
HTMX history management integrates AJAX-driven content changes with the browser's back and forward buttons, maintaining navigation state through URL updates and content Caching.
What You'll Learn
By the end of this tutorial, you'll use hx-push-url to update the URL, hx-replace-url to replace without history entry, configure the history cache, restore content from history, and handle history events.
Why It Matters
Users expect the back button to work. Without history management, AJAX navigation breaks the browser's natural navigation flow, confusing users who expect to return to the previous state.
Real-World Use
Doda Browser's file manager uses HTMX history for folder navigation. Each folder click uses hx-push-url to update the URL. The back button restores the previous folder contents from the history cache without a server request.
Where This Fits in Your Learning Path
flowchart LR
A["Synchronization"] --> B["**History Management**"]
B --> C["Indicators & UX"]
C --> D["Hyperscript & Extensions"]
D --> E["Advanced HTMX"]
style B fill:#3b82f6,stroke:#2563eb,color:#fff
style A fill:#e2e8f0,stroke:#94a3b8
style E fill:#e2e8f0,stroke:#94a3b8
Pushing to History with hx-push-url
Update the browser URL and add an entry to the history stack.
<a hx-get="/page/about" hx-target="#content" hx-push-url="true">
About Us
</a>
<a hx-get="/page/contact" hx-target="#content" hx-push-url="true">
Contact
</a>
<div id="content">
Main content area
</div>
Expected output: Clicking a link updates the content area and changes the URL. The back button returns to the previous URL and restores the previous content.
Setting a Specific URL
hx-push-url can also take a specific URL string.
<button hx-get="/api/products/123"
hx-target="#product-detail"
hx-push-url="/products/123">
View Product
</button>
<!-- Push a modified URL -->
<button hx-post="/api/search"
hx-target="#results"
hx-push-url="/search?q=keyword">
Search
</button>
Expected output: The browser URL changes to the specified path instead of the request URL.
Replacing Without History Entry
Use hx-replace-url to update the URL without adding a history entry.
<!-- Replace URL: back button skips this state -->
<div hx-get="/page/step2"
hx-target="#wizard"
hx-replace-url="true">
Step 2 of wizard
</div>
<!-- Useful for search filters: don't pollute history -->
<input hx-get="/api/filter"
hx-target="#results"
hx-replace-url="true"
hx-trigger="input changed delay:500ms">
Expected output: The URL updates but no history entry is added. The back button goes to the page before the filter was applied.
History Cache Configuration
Control how HTMX caches pages for history restoration.
<!-- Configure cache size in htmx.config -->
<script>
htmx.config.historyCacheSize = 20 // Cache up to 20 pages
htmx.config.refreshOnHistoryMiss = false // Don't refresh on cache miss
</script>
<!-- Individual element caching -->
<div hx-get="/api/slow-page"
hx-push-url="true"
hx-history="true">
This page will be cached for back-button
</div>
<div hx-get="/api/live-data"
hx-push-url="true"
hx-history="false">
This page will NOT be cached (always refetch)
</div>
Expected output: Cached pages restore instantly from memory. Non-cached pages always fetch fresh content from the server.
History Events
Listen for history restoration events to run custom logic.
<script>
document.addEventListener('htmx:historyRestore', function(event) {
console.log('History restored:', event.detail.path)
// Reinitialize components that need it
updateActiveNav(event.detail.path)
})
document.addEventListener('htmx:beforeHistorySave', function(event) {
console.log('Saving history state for:', event.detail.path)
// Save scroll position or other state
event.detail.scrollPos = window.scrollY
})
</script>
Expected output: When the user navigates back, the historyRestore event fires, allowing custom reinitialization logic.
Disabling History for Specific Elements
Prevent specific requests from creating history entries.
<!-- Polling should not create history entries -->
<div hx-get="/api/status"
hx-trigger="every 10s"
hx-push-url="false">
Status: <span id="status-value">Checking...</span>
</div>
<!-- Delete actions shouldn't be in history -->
<button hx-delete="/api/items/1"
hx-target="#item-1"
hx-swap="outerHTML"
hx-push-url="false">
Delete
</button>
Expected output: Polling updates and delete actions do not pollute the browser history.
Common Mistakes
1. Using hx-push-url on every request
Not every request needs history. Only push URL for navigational changes (page views, filter changes). Skip for polling, saves, and deletes.
2. Forgetting that cached pages don't execute JavaScript
History cache restores HTML but may not reinitialize scripts. Use htmx:historyRestore event to reinitialize components.
3. Not setting hx-push-url on initial page load
The first page needs a URL. If the initial content is loaded via HTMX without push-url, the back button may show a blank page.
4. Overwriting history with replace-url
replace-url updates the current entry. Using it on every scroll or input change makes the history stack useless.
5. Expecting history to work with POST requests
HTMX history primarily works with GET requests. POST history behavior is less reliable and should be avoided.
Practice Questions
What does hx-push-url do? It adds an entry to the browser history stack with the current URL, enabling back/forward navigation.
What is the difference between hx-push-url and hx-replace-url? push-url adds a new history entry. replace-url updates the current history entry without adding a new one.
How does HTMX cache pages for history? HTMX stores the HTML content of pages in memory (default 10 entries). When navigating back, it restores from cache instead of fetching.
What event fires when restoring from history? htmx:historyRestore fires when the user navigates back/forward to a cached page.
When should you NOT use hx-push-url? For polling, form submissions, delete actions, and any non-navigational updates.
Challenge
Build a multi-page product catalog where each product page uses hx-push-url for URL updates. The back button should restore the previous product list from cache. Add a live search that uses hx-replace-url to update the URL without adding history entries.
FAQ
Mini Project
Build a tabbed documentation browser. Each tab loads content via hx-get with hx-push-url. The back button restores the previous tab. A search field uses hx-replace-url to update the URL without polluting history.
<nav class="tabs">
<a hx-get="/docs/getting-started"
hx-target="#doc-content"
hx-push-url="true"
class="tab">Getting Started</a>
<a hx-get="/docs/api-reference"
hx-target="#doc-content"
hx-push-url="true"
class="tab">API Reference</a>
<a hx-get="/docs/examples"
hx-target="#doc-content"
hx-push-url="true"
class="tab">Examples</a>
</nav>
<input type="search"
hx-get="/docs/search"
hx-target="#doc-content"
hx-replace-url="true"
hx-trigger="input changed delay:300ms"
placeholder="Search documentation...">
<div id="doc-content">
Select a topic to begin
</div>
<script>
document.addEventListener('htmx:historyRestore', function() {
document.querySelectorAll('.tab').forEach(t => {
t.classList.remove('active')
})
const activeTab = document.querySelector(`.tab[href="${location.pathname}"]`)
if (activeTab) activeTab.classList.add('active')
})
</script>
What's Next
Create loading indicators:
| Tutorial | What You'll Learn |
|---|---|
| HTMX Indicators | Loading indicators and UX patterns |
| Hyperscript | Enhancing HTMX with Hyperscript |
Related topics: History API (pushState, popState), browser navigation patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro