# Navigation API

> How window.navigation and the navigate event let single-page apps intercept and control browser navigations, with Chrome, Firefox, and Safari support versions.

**In one line:** per MDN, "the Navigation API provides the ability to initiate,
intercept, and manage browser navigation actions," as a successor to the History API
and `window.location` aimed at the needs of single-page applications (SPAs).

## What it is

The API is accessed via `Window.navigation`, which per MDN "returns a reference to a
global `Navigation` object" — each `window` has its own instance. Its most notable
event is `navigate`, which per MDN's Navigation API overview "is fired when any type
of navigation is initiated, meaning that you can control all page navigations from
one central place, ideal for routing functionality in SPA frameworks." MDN's
`navigate_event` reference describes the same event more narrowly: it "fires when any
type of navigation is initiated, allowing you to intercept as required."

## Where it is supported

Per MDN's browser-compat-data, the `Navigation` interface is supported in Chrome from
version 102 (Chrome for Android from 102), in Firefox from version 147, and in Safari
from version 26.2 (Safari on iOS from 26.2) — the compat data marks Edge, Opera, and
Samsung Internet entries as `"mirror"`, meaning their support data is derived from the
corresponding upstream Chromium release rather than stated directly; it is not a claim
that those browsers share Chrome's own version numbers. The `NavigateEvent` interface itself shipped alongside it, also
from Chrome 102; only its current `intercept()` method arrived slightly later, at
Chrome 105 (an earlier, differently-named method, `transitionWhile`, existed from
Chrome 102 until it was removed at Chrome 108). Firefox 147 and Safari 26.2 shipped
`NavigateEvent` and `intercept()` together.

## How to use it

Listen for the `navigate` event and call `intercept()` to handle the navigation with
custom logic instead of a full page load. This example assumes the page's app shell
already renders a `<div id="article"></div>` container to update:

```js
navigation.addEventListener("navigate", (event) => {
  // Some navigations, e.g. cross-origin navigations, cannot be intercepted —
  // let the browser handle those normally.
  if (!event.canIntercept) {
    return;
  }
  // Don't intercept fragment navigations or downloads.
  if (event.hashChange || event.downloadRequest !== null) {
    return;
  }

  const url = new URL(event.destination.url);

  event.intercept({
    async handler() {
      renderArticlePagePlaceholder();
      const articleContent = await getArticleContent(url.pathname);
      renderArticlePage(articleContent);
    },
  });
});

function renderArticlePagePlaceholder() {
  document.querySelector("#article").innerHTML = "<p>Loading…</p>";
}

async function getArticleContent(pathname) {
  const response = await fetch(`/api/articles${pathname}`);
  return response.text();
}

function renderArticlePage(html) {
  document.querySelector("#article").innerHTML = html;
}
```

Per MDN's `navigate_event` reference, the URL has already updated by the time
`handler()` runs, so its own example renders a placeholder immediately and swaps in
real content once it is fetched — the pattern used above.

## How to detect it at runtime

Feature-detect both `navigation` on `window` and `NavigateEvent.prototype.intercept`
before registering handlers — per the compat data above, `window.navigation` shipped in
Chrome 102 but `intercept()` did not arrive until Chrome 105, so checking `window`
alone lets Chrome 102–104 through and then throws a `TypeError` on the first
navigation. Fall back to real anchor-tag/History API navigation the browser already
performs. This example assumes markup with a `<div id="article"></div>` container and
in-page links (e.g. `<a href="/articles/1">`) already exist on the page:

```js
function supportsNavigationApi() {
  return (
    "navigation" in window &&
    typeof NavigateEvent !== "undefined" &&
    typeof NavigateEvent.prototype.intercept === "function"
  );
}

if (supportsNavigationApi()) {
  navigation.addEventListener("navigate", handleAppNavigation);
} else {
  // No usable Navigation API here — rely on regular link clicks and
  // History API pushState/popstate for routing instead.
  setupHistoryApiRouting();
}

function handleAppNavigation(event) {
  if (
    !event.canIntercept ||
    event.hashChange ||
    event.downloadRequest !== null ||
    typeof event.intercept !== "function"
  ) {
    return;
  }
  const url = new URL(event.destination.url);
  event.intercept({
    async handler() {
      const response = await fetch(`/api/articles${url.pathname}`);
      document.querySelector("#article").innerHTML = await response.text();
    },
  });
}

function setupHistoryApiRouting() {
  // Intercept same-origin link clicks so they push a new History entry
  // instead of triggering a full page load.
  document.addEventListener("click", (event) => {
    const link = event.target.closest("a[href]");
    if (
      !link ||
      link.origin !== location.origin ||
      event.defaultPrevented ||
      event.button !== 0 ||
      event.metaKey ||
      event.ctrlKey ||
      event.shiftKey ||
      event.altKey
    ) {
      return;
    }
    event.preventDefault();
    history.pushState(null, "", link.href);
    renderArticleFromPath(location.pathname);
  });

  // Render the article matching the address bar on back/forward navigation.
  window.addEventListener("popstate", () => {
    renderArticleFromPath(location.pathname);
  });

  renderArticleFromPath(location.pathname);
}

async function renderArticleFromPath(pathname) {
  const response = await fetch(`/api/articles${pathname}`);
  document.querySelector("#article").innerHTML = await response.text();
}
```

## Practical checklist

- Per MDN's `navigate_event` reference, exit early ("Exit early if this navigation
  shouldn't be intercepted") for cases like `event.hashChange` or
  `event.downloadRequest` — not every `navigate` event should be intercepted.
- Chrome's `intercept()` method replaced an earlier, removed method named
  `transitionWhile` — code written against Chrome 102–107 samples using
  `transitionWhile` will not run on current browsers.
- Per MDN's compat data, `NavigateEvent`/`intercept()` landed in Firefox and Safari
  only at version 147 and 26.2 respectively, well after Chrome — sites still need the
  History API fallback path for any browser below those versions.
- The `navigate` event is central to all navigation types per MDN, but not all of
  them can be intercepted: per MDN's `NavigateEvent.canIntercept` reference, cross-origin
  navigations are one case where `canIntercept` is `false`, so a handler must check it
  and let the browser handle those navigations normally instead of trying to intercept
  them.

## Where to go next

- [Document Picture-in-Picture: always-on-top window](/reference/capabilities/document-picture-in-picture/)
- [Window Management](/reference/capabilities/window-management/)