History and Navigation

History pushState and the Navigation API

history.pushState(state, '', url) and replaceState() change the URL without a page load, and popstate reports back and forward (Accessing History), but routers must still intercept every link click and form submission. The Navigation API, window.navigation, replaces that plumbing: one navigate event fires for link clicks, form submissions, navigation.navigate(), history.pushState() and the back button, and event.intercept() turns the navigation into a same-document update. It became Baseline Newly available in January 2026 (Chrome 102 1 , Safari 26.2 10 , Firefox 147 555 ).

A minimal router with the Navigation APIJavaScript
navigation.addEventListener('navigate', (event) => {
  const url = new URL(event.destination.url);
  if (!event.canIntercept || event.hashChange || !url.pathname.startsWith('/app/')) return;
  event.intercept({
    async handler() {                          // runs instead of a full page load
      document.querySelector('main').textContent = `Rendered ${url.pathname}`;
      console.log(event.navigationType, url.pathname);
    },
  });
});
await navigation.navigate('/app/products').finished;   // <a> clicks and forms land here too
await navigation.navigate('/app/cart', { state: { items: 3 } }).finished;
await navigation.back().finished;
history.pushState(null, '', '/app/help');              // the old API is intercepted too
Output
push /app/products
push /app/cart
traverse /app/products
push /app/help

When the handler's promise settles, the browser resets focus and restores or sets the scroll position (opt out with focusReset: 'manual' and scroll: 'manual'); pass event.signal to fetch() so a stopped or superseded navigation cancels its requests. Keep a History API fallback for older browsers.