Skip to content

Navigation

routekit gives you three ways to change URLs: native <a> clicks (intercepted automatically), programmatic router.go(), and direct history.pushState if you want to bypass routekit’s hooks.

When you call router.start(), routekit attaches a single delegated click handler at document. Any in-app <a href="..."> click that:

  • has a same-origin URL,
  • is not a download (target="_blank", download, [rel="external"]),
  • is not a modified click (cmd-click, middle-click),

is intercepted: routekit calls event.preventDefault() and router.go(href) instead. Native browser behavior is preserved otherwise.

<!-- Just write normal HTML; routekit handles the rest -->
<a href="/posts/42">Read post 42</a>
<a href="/posts.csv" download>Export</a> <!-- bypassed, native -->
<a href="https://example.com">External</a> <!-- bypassed, native -->
<a href="/edit" target="_blank">Edit (new tab)</a> <!-- bypassed, native -->

Programmatic navigation

router.go('/posts/42'); // pushes a new history entry
router.replace('/login'); // replaces current history entry
router.back(); // history.back()
router.forward(); // history.forward()

go() and replace() accept either a path string or an object:

router.go('/posts/42');
router.go({ path: '/posts/42', state: { from: 'newsletter' } });

The state is exposed on subsequent match.state reads. Use this for back-button-friendly flows (e.g. preserving scroll position, modal open/close state).

There’s no <NavLink> component — instead, attach a CSS class to your <a> element and let the browser do the work:

import { isActive } from 'routekit';
document.querySelectorAll('a[href]').forEach((a) => {
a.toggleAttribute('data-active', isActive(a.getAttribute('href')!, router.url));
});

Then style with [data-active]:

nav a[data-active] {
color: var(--accent);
font-weight: 600;
}

isActive(href, currentUrl) returns true when:

  • exact match (/users/users), OR
  • prefix match with a trailing slash (/users//users/42).

If you want stricter matching (exact only), pass { exact: true }:

isActive('/users', router.url, { exact: true });

Cross-origin <a> clicks are not intercepted — they trigger normal browser navigation away from your app. If you want to confirm before leaving (for unsaved-form prompts, e.g.), use the standard beforeunload event:

window.addEventListener('beforeunload', (e) => {
if (formIsDirty) {
e.preventDefault();
e.returnValue = '';
}
});

routekit deliberately doesn’t expose a “before-navigate” hook. The platform already has one.

Scroll restoration

routekit doesn’t manage scroll. By default, router.go() does NOT scroll to the top — your app keeps its current scroll position. To match the typical SPA expectation:

router.on('navigate', ({ trigger }) => {
if (trigger === 'push') {
// Forward navigation: scroll to top.
window.scrollTo({ top: 0, behavior: 'instant' });
}
// 'pop' (back/forward button): browser handles scroll restoration.
});

The trigger field disambiguates pushed vs. popped navigations so you only scroll on forward moves.