demo · v141

Lazy Route Redirect

Auth-gated route. The user navigates to /secret; before the URL commits, a precommit handler checks auth, decides "nope", and redirects the still-pending navigation to /login?next=/secret. The URL bar never blinks past the protected URL — the entire flow is one navigation entry.

Heads up Requires Chrome 141+. The pre-141 alternative — let the URL flip, render an interstitial, then call history.replaceState — leaves a brief "you tried to see /secret" leak in the address bar and double-counts the page view. Precommit fixes both.
checking support…
simulated URL bar: /home
welcome

the call

navigation.addEventListener("navigate", (e) => {
  if (!e.canIntercept || e.hashChange) return;
  e.intercept({
    // NEW in 141: precommit phase — URL still hasn't flipped
    async precommitHandler(controller) {
      const url = new URL(e.destination.url);
      if (url.pathname.startsWith("/secret") && !isAuthed()) {
        // Redirect the pending nav. URL bar will commit to the new URL,
        // not the original one.
        await controller.redirect("/login?next=" + encodeURIComponent(url.pathname));
      }
    },
    async handler() {
      await renderRoute(location.pathname);
    },
  });
});

why this angle

The WICG navigation-api issue #66 — the one the chromestatus entry directly cites as motivating this feature — was filed by routing-library authors. React Router, TanStack Router, Vue Router all need to gate navigation on async checks (auth, data prefetch, route guard) before the URL commits. Without precommit, the URL flipped first and routers had to choose between a flash of the wrong content or shimming with replaceState afterward. Precommit lets the router intercept, redirect or cancel, all without the URL ever showing the user something they're not allowed to see.

see also