Which link clicks must a client-side router leave to the browser instead of intercepting them?
answer
- the router upgrades, it does not own
- user intent overrides the app
- button, modifiers, target, download
- resolve the address, do not pattern-match
- two leading slashes is another origin
basics
~20 sLeave anything the browser handles differently: a non-primary or modified click, a link aimed at another tab or frame, a download, another origin or scheme, a same-document fragment. Intercepting those breaks behaviour users rely on.
solid answer
~40 sInterception is a listener that finds the nearest link for the clicked node and only then decides whether to cancel the browser's default. The checks before cancelling: the primary button, with no modifier key held; the event not already cancelled by something else; a link with a real address; no target pointing at another browsing context; no download; the resolved address same-origin, in the router's scheme, and inside the path the router owns; and not a bare fragment on the current document. A `starts with a slash` test is not an origin test — an address beginning with two slashes is another origin. If any check fails, return without cancelling and let the browser do its job. Only when all of them pass does the router cancel, record the entry and render the route.
go deeper
Know that the router only takes over plain left clicks on links inside the app, and that clicks with a modifier key, downloads and external addresses are left to the browser.
Be able to recite the check list and say why each item exists, and to explain that the default is cancelled only after every check passes rather than up front.
Talk about the cases teams actually ship broken: content-system markup outside the components, shadow boundaries, nested controls that must stop the click, and an origin test done by string prefix.
The judgment call is where this decision lives — one delegated listener as a platform concern versus per-component handling — and how you stop a second, subtly different copy of the check list from appearing in another team's code.
## What interception actually is A client-side router does not implement links; it *intercepts* them. Typically one delegated click listener sits near the root of the tree so that links rendered anywhere — including markup the app did not author, such as body copy from a content system — are covered. On each click the listener walks up from the clicked node to the nearest enclosing link, decides whether this click is one the router owns, and only then cancels the browser's default document navigation, records a history entry and renders the matching route. The entire engineering content of the feature is the decision. Cancel too eagerly and you break behaviours the browser implements better than you ever will, and that users perform without thinking. ## The checks, roughly in order 1. **Primary button only.** A middle-click is the browser's open-in-a-new-tab gesture; a secondary click opens the context menu. Neither belongs to the app. 2. **No modifier key held.** A modifier plus click means new tab, new window, or in some browsers download. That is explicit user intent to override the page. 3. **The event has not already been cancelled** by a handler closer to the click, and propagation has not been stopped by a nested control that meant to handle it itself. 4. **There is a link with a real address**, found through the event's composed path so that links inside a shadow boundary are still discovered. 5. **No target pointing elsewhere.** A link aimed at a new context or a frame is not a navigation of this document at all. 6. **Not a download.** A link marked as a download must produce a file, not a route change. 7. **Same scheme and same origin, inside the path the router owns.** Resolve the address against the current document rather than pattern-matching the raw string: an address beginning with two slashes is cross-origin, and a mail, telephone or custom application scheme belongs to the platform and its handlers. 8. **Not a bare fragment on the current document.** Jump-to-heading is platform behaviour; own it only if the router deliberately treats the fragment as part of its address. If all of that passes, the router cancels the default, decides whether this is a new place or a correction of the current one, and applies its restoration policy to the screen it renders. Whether an address that matches no route is handled in-app or handed to the server is a route-table decision, not an interception decision. ## What breaks when the list is short | Click the router wrongly intercepts | What the user sees | |---|---| | Modified or middle click | The requested tab never opens; the current screen changes instead | | Cross-origin link | A blank screen or an in-app not-found for somebody else's address | | Download link | No file; the app tries to route to the file's address | | Link targeting another context | The other tab or frame stays empty | | Mail, telephone or custom scheme | The external handler never opens | | Same-document fragment | The jump to the heading is replaced by a re-render | Each of these reads to the user as the app being broken in a small, memorable way, and each is caught by one comparison. ## Where the listener lives, and the tradeoff - **One delegated listener** covers every link on the page, including markup outside the app's own components, and it is the only approach that works for user-authored content. It must be defensive, because it sees clicks it has no business in. - **A per-link component** that handles its own click is explicit and easy to reason about, but misses links the app did not render, and tends to grow one copy of the check list per component. - Either way, a nested interactive element that stops propagation must win, and a link rendered into a detached container elsewhere in the document still needs the same decision applied. ## The rule of thumb Intercept only the clicks whose behaviour you are actually reproducing. A router can reproduce *navigate this document to an address I own*. It cannot reproduce open-in-a-new-tab, download, the context menu, or a handler for another scheme — so those clicks are not its business, and the correct action is to do nothing at all and let the default proceed.
- Why is testing that the address starts with a slash not a same-origin test?An address beginning with two slashes keeps the current scheme but takes a new host, so it passes a leading-slash test and still leaves your site. Resolve the address against the current document and compare the resulting origin, which also handles relative paths, dot segments and a router mounted under a base path correctly.
- Why prefer one delegated listener over a click handler on every link component?Because not every link comes from a component. Body copy, imported documents and legacy regions of the page produce plain links, and a delegated listener near the root upgrades those too. The cost is that it sees clicks it does not own, which is exactly why the check list must be explicit rather than implied by who rendered the element.
- A nested control inside a link needs its own click behaviour. What must happen?The inner control handles the click and stops it travelling further, so the router's listener never treats it as a link activation. Nesting an interactive control inside a link is best avoided altogether; when it is unavoidable, the inner handler must both cancel the default and stop propagation, and the router must respect an already-cancelled event.
saying these in an interview costs you the question
- Cancel every click and let the router decide afterwards
- A modifier-click is just a click with a key held
- Checking the origin is enough on its own
- An address starting with a slash is always internal
- Download links behave like ordinary links
- Fragment links should be handled by the router for consistency