Web APIs, todayLab · Alex Harto

Navigation API

One event for every navigation: the standard router that replaces pushState and popstate.

In your browser: checking…Reviewed 1 Oct 2026

navigation.addEventListener('navigate') receives links, forms, back and forward, reloads and location changes. With event.intercept() the page decides what to render without reloading, and the browser manages history, focus and scroll.

It has been Baseline since 13 January 2026 (Firefox 147, with Safari 26.2 a month earlier). precommitHandler, to redirect or cancel before the URL changes, is in Chrome 141 and Firefox 147, and is part of Interop 2026.

What it is for

  • Single-page app routers without libraries
  • Intercepting navigations to save pending changes
  • Actually knowing what is in the history (navigation.entries())

Demo

A mini app inside this page: links change the URL (?vista=…) without reloading, and the browser’s back and forward work too. “Private area” redirects before the URL changes (precommitHandler).

Browser support

Baseline · newly available · 13 Jan 2026
FeatureDesktopMobile
ChromeEdgeFirefoxSafariChrome AndroidFirefox AndroidSafari iOS
Navigation (window.navigation)102Chrome: 102 102Edge: 102 147Firefox: 147 26.2Safari: 26.2 102Chrome: 102 147Firefox: 147 26.2Safari: 26.2
intercept({ precommitHandler })141Chrome: 141 141Edge: 141 147Firefox: 147 —Safari: No141Chrome: 141 147Firefox: 147 —Safari: No

Compatibility generated on every deploy from MDN browser-compat-data 8.1.3 (24 Sep 2026) · web-features 3.40.0 · checked by hand: 1 Oct 2026

Minimal code

<!doctype html>
<meta charset="utf-8">
<title>Navigation API · ejemplo mínimo</title>
<!-- Un enrutador de una sola página con la Navigation API. Sírvelo por HTTP (npx serve .), no con file://. -->
<nav>
  <a href="?vista=inicio">Inicio</a> ·
  <a href="?vista=obra">Obra</a> ·
  <a href="?vista=contacto">Contacto</a> ·
  <button id="back">Atrás</button>
</nav>
<main id="view"></main>
<script type="module">
const view = document.querySelector('#view');
const pages = { inicio: 'Bienvenida', obra: 'Trabajos', contacto: 'Escríbeme' };
const render = (url) => {
  const vista = new URL(url).searchParams.get('vista') ?? 'inicio';
  view.textContent = pages[vista] ?? 'No encontrada';
};

if (!('navigation' in window)) {
  view.textContent = 'Sin Navigation API: los enlaces recargan la página.';
} else {
  render(location.href);
  // Un único punto para todas las navegaciones: enlaces, formularios, atrás/adelante, location…
  navigation.addEventListener('navigate', (event) => {
    if (!event.canIntercept || event.hashChange || event.downloadRequest) return;
    const url = new URL(event.destination.url);
    if (url.pathname !== location.pathname) return; // deja pasar lo que no es de esta página
    event.intercept({
      async handler() {
        await new Promise((r) => setTimeout(r, 150)); // aquí iría un fetch
        render(url);
      },
    });
  });
  document.querySelector('#back').onclick = () => navigation.canGoBack && navigation.back();
  navigation.addEventListener('navigatesuccess', () => console.log('Entradas:', navigation.entries().length));
}
</script>

What’s new

  1. Chrome 146 adds addHandler() for the precommitHandler (Firefox 148).
  2. It joins Interop 2026, with precommitHandler.
  3. Firefox 147 ships it: Baseline.
  4. Safari 26.2 ships it.

Keep in mind

  1. Check canIntercept: cross-origin navigations cannot be intercepted.
  2. Passing precommitHandler on a non-cancelable event throws a SecurityError.
  3. Let downloads and hash changes through (downloadRequest, hashChange).

Further reading

Sources