htmx 4.0 is under construction — migration guide

History Cache

Client-side history cache in sessionStorage — restores pages instantly on back/forward navigation without a network request

The history-cache extension replaces htmx’s default history handling with a client-side cache stored in sessionStorage. When the user navigates back or forward, the extension restores the page instantly from cache instead of fetching from the server.

Installing

<script src="/path/to/htmx.js"></script> <script src="/path/to/ext/hx-history-cache.js"></script>

Usage

No markup changes are required. Once the script is loaded, all htmx-driven navigation is cached automatically.

To exclude a page from being saved to the cache, add hx-history="false" anywhere in the document:

<div hx-history="false"> <!-- This page will not be saved to the history cache --> </div>

Configuration

All options live under htmx.config.historyCache and can be set via a meta tag:

<meta name="htmx-config" content='{"historyCache": {"size": 20, "refreshOnMiss": true}}'>

To use morphing for smoother restores:

<meta name="htmx-config" content='{"historyCache": {"swapStyle": "innerMorph"}}'>
OptionDefaultDescription
size10Maximum number of pages to keep in the cache. Oldest entries are evicted first. Set to 0 to disable caching entirely.
refreshOnMissfalseWhen true, forces a full page reload if the requested history entry is not in the cache.
disablefalseDisables the extension without unloading it.
swapStyle"innerHTML"The htmx swap style used when restoring cached content. Can be set to "innerMorph" for smooth DOM diffing instead of a full replacement.

Events

The extension fires the following events on document:

EventDetailDescription
htmx:history:cache:before:save{ path, target, cache }Fired before saving the current page. Return false or set detail.cancelled to skip saving. Mutations to path, target, and cache are respected.
htmx:history:cache:after:save{ path, item, cache }Fired after a page is successfully saved.
htmx:history:cache:miss{ path, refreshOnMiss }Fired when the requested history entry is not in the cache. Set detail.refreshOnMiss = true to force a reload.
htmx:history:cache:hit{ path, item }Fired when a cache entry is found. Return false to cancel the cache restore and let htmx fetch from the server instead.
htmx:history:cache:restored{ path, item, head }Fired after content has been restored from the cache.

Example: Skipping the cache for specific paths

document.addEventListener('htmx:history:cache:before:save', (evt) => { if (evt.detail.path.startsWith('/admin')) { evt.detail.cancelled = true; } });

Example: Handling a cache miss

document.addEventListener('htmx:history:cache:miss', (evt) => { console.log('Cache miss for', evt.detail.path); evt.detail.refreshOnMiss = true; // reload instead of fetching via htmx });

Example: Inspecting a cache hit before restore

document.addEventListener('htmx:history:cache:hit', (evt) => { if (isStale(evt.detail.item)) { return false; // bypass cache, let htmx fetch fresh content } });

Head Restoration

By default the extension saves the <head> snapshot but does not restore it. Including the head-support extension enables full <head> restoration on cache hits — styles, scripts, and meta tags are merged back in alongside the body content.

<script src="/path/to/ext/hx-history-cache.js"></script> <script src="/path/to/ext/hx-head-support.js"></script>

How It Works

  1. Before navigation (htmx_before_history_update): the current page’s HTML, <head>, title, and scroll position are serialised and written to sessionStorage.
  2. On back/forward (htmx_before_restore_history): the extension looks up the target path in the cache.
    • Hit: fires htmx:history:cache:hit, sets detail.cancelled = true, and restores content via htmx.swap(). Core never makes a network request. If head-support is loaded, the saved <head> is also restored via htmx:history:cache:restored.
    • Miss: fires htmx:history:cache:miss. If refreshOnMiss is set the page reloads; otherwise core handles the fetch normally.
  3. Cache eviction: when the cache exceeds size, the oldest entry is dropped. If sessionStorage is full, entries are dropped from the front until the write succeeds.