v1.0.0 · stable 0 dependencies ~3KB gzipped TypeScript-first MIT

history.js

The modern, typed, framework-agnostic wrapper over the browser History API.

0
Pushes
0
Replaces
0
Pops
1
Stack size
Live playground

Every action mutates the URL bar above

This whole page runs on the bundled library. Open DevTools and watch window.history change in real time.

Navigate push / replace

Push or replace an entry. Replace keeps the same index — push creates a new one.

Query helpers setQuery

Patch query params on the current URL. null removes a key.

Route matcher matches

Test a pattern against the current pathname. Supports :param, :param?, :param*.

Result will appear here.

Navigation guard addGuard

When enabled, every navigation triggers a confirm() prompt. Cancel it to block the navigation.

Link interception interceptLinks

Same-origin <a> clicks become push calls automatically. Try them:

Event log subscribe

Every navigation streams a typed NavigationEvent here.

Virtual stack entries

In-memory snapshot of entries seen by the manager. Current entry is highlighted.

What you get

A pragmatic toolbox for routing

Not a router — the primitives a router is built from. Use it directly, or wrap it in your own thin abstraction.

📦

Typed entries

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
🔔

Subscription model

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();
🛡️

Async guards

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();
});
🔗

Query helpers

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');
🎯

Route matcher

Pattern matching for routing decisions. Supports named, optional, and splat params.

h.matches<{ id: string }>('/users/:id');
buildPath('/users/:id', { id: 42 });
// → '/users/42'
🪝

Link interception

One call hijacks every <a> click. Modifier-clicks, off-origin, target, and download links pass through.

import { interceptLinks } from '@buildwithdarsh/history.js';
interceptLinks(h);
📜

Virtual stack

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);
});
🔄

Scroll restoration

Automatic on pop navigations — preserves scroll position per entry, just like the browser used to.

const h = getHistory({
  restoreScrollOnPop: true,
});

Promise-friendly

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
Get started

Install in one line

Works in any bundler, plain <script>, or as an ES module.

$ npm install @buildwithdarsh/history.js