The modern, typed, framework-agnostic wrapper over the browser History API.
This whole page runs on the bundled library. Open DevTools and watch window.history change in real time.
Push or replace an entry. Replace keeps the same index — push creates a new one.
Patch query params on the current URL. null removes a key.
Test a pattern against the current pathname. Supports :param, :param?, :param*.
When enabled, every navigation triggers a confirm() prompt. Cancel it to block the navigation.
Same-origin <a> clicks become push calls automatically. Try them:
Every navigation streams a typed NavigationEvent here.
In-memory snapshot of entries seen by the manager. Current entry is highlighted.
Not a router — the primitives a router is built from. Use it directly, or wrap it in your own thin abstraction.
Every navigation produces a HistoryEntry<TState> with a stable id, monotonic index, timestamp, and location snapshot.
const h = getHistory<{ view: 'home' }>(); h.entry.id; // 'hjs_…' h.entry.state; // { view: 'home' } | null
Plain functions in, unsubscribe out. No event-name strings, no statechange magic.
const off = h.subscribe((e) => { console.log(e.type, e.to.location.pathname); }); off();
Return false to cancel. Pop navigations attempt to restore via history.go(delta).
h.addGuard(async (e) => { if (e.to.location.pathname.startsWith('/admin')) return await checkAuth(); });
Patch params without rebuilding the URL. Null removes a key. Arrays serialize as repeated params.
h.setQuery({ page: 2, q: 'hi' }); h.setQuery({ page: null }); // remove h.getQuery('page');
Pattern matching for routing decisions. Supports named, optional, and splat params.
h.matches<{ id: string }>('/users/:id'); buildPath('/users/:id', { id: 42 }); // → '/users/42'
One call hijacks every <a> click. Modifier-clicks, off-origin, target, and download links pass through.
import { interceptLinks } from '@buildwithdarsh/history.js'; interceptLinks(h);
Tracks every entry the manager has visited so you can render breadcrumbs, history menus, or analytics.
h.entries.forEach((entry) => {
console.log(entry.index, entry.location.href);
});
Automatic on pop navigations — preserves scroll position per entry, just like the browser used to.
const h = getHistory({ restoreScrollOnPop: true, });
Navigation is async because guards are async. Resolves with true or false so you know what happened.
const ok = await h.push('/profile'); if (!ok) showBlockedToast(); await h.waitFor(); // next nav
Works in any bundler, plain <script>, or as an ES module.
$ npm install @buildwithdarsh/history.js