Navigation API
One event for every navigation: the standard router that replaces pushState and popstate.
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| Feature | Desktop | Mobile | |||||
|---|---|---|---|---|---|---|---|
| Chrome | Edge | Firefox | Safari | Chrome Android | Firefox Android | Safari 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: No | 141Chrome: 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><!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
Keep in mind
- Check canIntercept: cross-origin navigations cannot be intercepted.
- Passing precommitHandler on a non-cancelable event throws a SecurityError.
- Let downloads and hash changes through (downloadRequest, hashChange).
Further reading
Sources
- web-features 3.40.0 · navigation (Baseline 2026-01-13)30 Sep 2026
- MDN · NavigateEvent.intercept()27 Aug 2026
- Chrome 146 release notes10 Mar 2026
- Interop 202612 Feb 2026