skip to content

Inside a `navigate` event listener on `window.navigation`, when is `event.canIntercept` false, and what should your code do in that case?

level: middleimportance: should knowfreq 32%

answer

  1. the browser decides, not you
  2. cross-origin cannot be same-document
  3. a download is not a navigation
  4. guard first, intercept second
  5. observing is still allowed

basics

~20 s

event.canIntercept is false when the browser cannot turn the navigation into a same-document one — a cross-origin destination, a download link, or a cross-document back/forward traversal. Return early and let the browser navigate; calling intercept() then throws.

solid answer

~50 s

`canIntercept` is the browser telling you whether this navigation can legally become a same-document one. It is `false` for a cross-origin destination, for a navigation that is really a download (`event.downloadRequest` is a non-null string, the filename from the link's `download` attribute), for a traversal to an entry that is not same-document, and for a document that is no longer fully active. The correct response is to do nothing — return from the listener and let the browser perform a real navigation. Calling `intercept()` anyway throws a `DOMException`, and an unguarded router breaks every outbound link on the page. The event still fires in those cases, so you can observe it for analytics or, when `event.cancelable` is true, block it — you just cannot take it over. In practice the first two lines of any `navigate` listener are the `canIntercept` and `downloadRequest` guards.

code

javascript · 12 lines
javascript
async function renderRoute(url) {
  document.querySelector("#app").textContent = new URL(url).pathname;
}

navigation.addEventListener("navigate", (event) => {
  if (event.downloadRequest !== null) {
    console.debug("download started:", event.downloadRequest || "(no filename)");
    return;
  }
  if (!event.canIntercept || event.hashChange) return;
  event.intercept({ handler: () => renderRoute(event.destination.url) });
});

go deeper

for a junior

Remember to check event.canIntercept before calling event.intercept(), and that a false value simply means you let the browser navigate normally.

for a middle

Be able to list the cases — cross-origin destination, download request, cross-document traversal, inactive document — and say why each cannot be rendered inside the current document.

for a senior

Talk about the production failure this prevents: an unguarded listener throwing on every outbound link, and using downloadRequest as a clean hook for observing downloads rather than wiring click handlers.

for a principal

Be able to argue where the boundary belongs — what the platform refuses to let a page take over and why — and how a team encodes those guards once so no route handler can forget them.

## What the flag means `event.canIntercept` on a `NavigateEvent` answers exactly one question: may this navigation be converted into a same-document navigation that your JavaScript completes? The browser computes it before your listener runs, and it is not advisory — `intercept()` throws a `DOMException` when the flag is `false`. ## The cases that make it false **Cross-origin destination.** A link to another site cannot be rendered inside your document; the same-origin policy is the whole reason. `event.destination.url` will have a different origin from the current document. This is the case that bites unguarded routers: every outbound link on the page starts throwing. **Download requests.** When the navigation came from a link with a `download` attribute, `event.downloadRequest` is a string rather than `null` — the requested filename, or an empty string if the attribute had no value. Downloads are not page navigations, so there is nothing to intercept. The flag is a genuinely useful hook even so: it is the one reliable place to notice "the user started a download" without wiring a click handler onto every link. **Cross-document traversals.** A back or forward navigation to an entry that was itself a full document load cannot be replayed as a same-document render — the browser has to restore or refetch that document. `event.destination.sameDocument` is `false` in that case. **A document that is no longer fully active**, for example one sitting inside a detached or hidden-away navigable. Note what is *not* in the list. A same-origin navigation to a completely different path — the ordinary SPA case — is interceptable even though the browser would otherwise fetch a new document. A same-origin form submission is interceptable too, and `event.formData` gives you the submitted entries. Those are the navigations the API exists to hand you. ## The shape of a correct listener ```js navigation.addEventListener("navigate", (event) => { if (event.downloadRequest !== null) return; // browser downloads the file if (!event.canIntercept) return; // cross-origin, or a real document load event.intercept({ handler: () => renderRoute(event.destination.url) }); }); ``` Guard first, intercept second. Returning early is not a failure mode — it is the browser doing the navigation the way it always did, which is exactly what you want for a link to another site. A subtlety worth stating: `canIntercept` is about *taking over*, not about *observing* or *cancelling*. The `navigate` event still fires for a cross-origin link click, so you can record the outbound click. And when `event.cancelable` is `true` you may still call `preventDefault()` to stop the navigation, which is how a form guard prevents the user leaving with unsaved edits regardless of where they were heading. ## Fragment-only navigations `event.hashChange` is `true` when only the fragment differs. These are same-document and interceptable, but you usually do not want to: intercepting means you take responsibility for scrolling to the target element. Many routers add `if (event.hashChange) return;` as a third guard so the browser keeps handling in-page anchors natively. ## Why interviewers ask It separates people who have read about the API from people who have shipped it. The demo snippets in blog posts often omit the guards; the first bug report from production is always "clicking a link to our docs site does nothing and the console shows an exception". Being able to name the four cases — cross-origin, download, cross-document traversal, inactive document — shows you have hit that and understand why the platform draws the line where it does.

  • What exactly does `event.downloadRequest` hold, and why is it separate from `canIntercept`?
    It is `null` for ordinary navigations and a string for one triggered by a link with a `download` attribute — the requested filename, or an empty string when the attribute has no value. It is separate because it carries information: it is the one place to observe that a download started, which `canIntercept` alone could not tell you.
  • A back navigation fires navigate but canIntercept is false. What does that tell you about the entry?
    That the destination is not same-document — `event.destination.sameDocument` is `false`, so the entry was a real document load and the browser must restore or refetch it rather than let you re-render. It usually means the user is traversing back past the point where your SPA took over, for example to the page they arrived from.
  • Should a router intercept fragment-only navigations?
    Usually not. `event.hashChange` is `true` for them and they are interceptable, but intercepting makes you responsible for scrolling to the target element and for the focus behaviour that comes with it. Unless the fragment is part of your routing scheme, return early and let the browser do what it already does well.

saying these in an interview costs you the question

  • Calls intercept() without checking canIntercept first
  • Thinks cross-origin links simply do not fire the navigate event
  • Believes any same-origin navigation to a new path is uninterceptable
  • Confuses downloadRequest being an empty string with it being null
  • Assumes canIntercept false also means you cannot cancel

context