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": "outerMorph"}}'>
| Option | Default | Description |
|---|---|---|
size | 10 | Maximum number of pages to keep in the cache. Oldest entries are evicted first. Set to 0 to disable caching entirely. |
refreshOnMiss | false | When true, forces a full page reload if the requested history entry is not in the cache. |
disable | false | Disables the extension without unloading it. |
swapStyle | "outerSync" | The htmx swap style used when restoring cached content. Defaults to outerSync, which preserves the target element in the DOM (keeping listeners and component state) while syncing its attributes and replacing its children. Use innerHTML to replace children only without syncing attributes. Can be set to "innerMorph" or "outerMorph" for smooth DOM diffing. |
Events
The extension fires the following events on document:
| Event | Detail | Description |
|---|---|---|
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
- Before navigation (
htmx_before_history_update): the current page’s HTML,<head>, title, and scroll position are serialised and written tosessionStorage. - On back/forward (
htmx_before_restore_history): the extension looks up the target path in the cache.- Hit: fires
htmx:history:cache:hit, setsdetail.cancelled = true, and restores content viahtmx.swap(). Core never makes a network request. Ifhead-supportis loaded, the saved<head>is also restored viahtmx:history:cache:restored. - Miss: fires
htmx:history:cache:miss. IfrefreshOnMissis set the page reloads; otherwise core handles the fetch normally.
- Hit: fires
- Cache eviction: when the cache exceeds
size, the oldest entry is dropped. IfsessionStorageis full, entries are dropped from the front until the write succeeds.