Skip to content

HTMX History Management — Complete Guide with Examples

DodaTech Updated 2026-06-28 5 min read

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

  1. What does hx-push-url do? It adds an entry to the browser history stack with the current URL, enabling back/forward navigation.

  2. 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.

  3. 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.

  4. What event fires when restoring from history? htmx:historyRestore fires when the user navigates back/forward to a cached page.

  5. 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

Does HTMX history work with pushState and popState?

Yes. HTMX uses history.pushState and popState under the hood, providing a standard-compliant history API.

How large is the default history cache?

The default is 10 entries. Configure with htmx.config.historyCacheSize.

Can I restore scroll position from history?

Yes. Save scrollY in the htmx:beforeHistorySave event and restore it in htmx:historyRestore.

Does history work with server-side redirects?

HTMX follows server redirects before pushing to history. The final URL after redirects is stored.

What happens when history cache is full?

The oldest entry is removed to make room for the new one (LRU eviction).


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