livewire/livewire: wire:navigate now falls back to the browser for external and native links
Contributed by MrPunyapal
Part of the livewire v4.4.7 release
If you are upgrading from Livewire v3.8.10 to a release that includes PR #10293, one of the most user-visible changes in the wire:navigate plugin is how it treats links it was never meant to handle. wire:navigate now only intercepts same-origin HTTP(S) page visits. External URLs, links with a target other than _self, download links, and non-HTTP schemes such as mailto: and tel: all fall back to the browser's default behavior. The result is fewer surprising redirects, correct new-tab and download behavior, and no more wasted prefetch requests on pages Livewire could never swap in.
What changed
Before this fix, the navigate plugin normalized every wire:navigate href against the current page and let its SPA loader fetch the result — even for absolute external URLs. This PR introduces a guard, linkShouldBeHandledNatively() in js/plugins/navigate/links.js, that runs before hover-prefetching and before click interception. When the guard says a link should be handled natively, Livewire never calls preventDefault() and never prefetches; the browser simply does what it would do if wire:navigate were not on the element at all.
The low-level link handler also accepts an optional shouldIntercept callback so native links are never prevented, and the wire:navigate documentation now states the fallback rule explicitly. New browser tests cover external, download, mailto:, and tel: links, asserting that the livewire:navigate event does not fire for any of them.
Why it matters: the external-link bug
The bug is easiest to see with the example from the PR:
<a href="https://github.com/pinkary-project/pinkary.com" target="_blank" wire:navigate>
Source Code
</a>
Instead of opening GitHub in a new tab, the absolute external URL was normalized into a same-origin path and fetched by the SPA loader — so a site at pinkary.com could open https://pinkary.com/pinkary-project/pinkary.com in the current tab. The same interception broke links with a download attribute (no file was downloaded), mailto: and tel: links (no mail client or dialer opened), and hover-prefetch burned requests on cross-origin URLs that could never be swapped into the page anyway.
How the fallback decision works
Simplified from the actual implementation, the guard checks four things:
export function linkShouldBeHandledNatively(linkEl, destination) {
if (! destination) return true
if (! ['http:', 'https:'].includes(destination.protocol)) return true
if (destination.origin !== window.location.origin) return true
if (linkEl.hasAttribute('download')) return true
let target = linkEl.getAttribute('target')?.trim().toLowerCase()
if (target && target !== '_self') return true
return false
}
A link is handed back to the browser when any of the following is true:
- The destination can't be resolved, or uses a non-HTTP(S) protocol (
mailto:,tel:,javascript:, and friends). - The destination origin differs from
window.location.origin(cross-origin). - The anchor has a
downloadattribute. - The anchor has a
targetother than_self, compared case-insensitively after trimming whitespace.
The check is applied consistently — on hover-prefetch, plain left clicks, programmatic .click() calls, Enter-key activation, and mouseup-based navigation — so there is no code path where a native link gets swallowed by the SPA loader.
Upgrade impact
Ordinary same-origin links are unaffected; this keeps working exactly as before:
<a href="/dashboard" wire:navigate.hover>Dashboard</a>
For most applications this is a pure bug fix with nothing to configure. That said, a few things deserve a quick audit as you move off v3.8.10:
livewire:navigatelisteners: the event no longer fires for external,download,mailto:,tel:, or non-_selflinks. If you used it for loading spinners or exit analytics, hook that logic into full page loads instead.- Tests: if any Dusk or browser tests asserted that external or
target="_blank"links were intercepted, they will fail — update them to expect native behavior. The new test inSupportNavigate/BrowserTest.phpis a good template. - Programmatic clicks:
element.click()on a bypassed link now triggers the browser's native behavior, not Livewire's loader. - Markup hygiene: external links carrying
wire:navigateare now safe, but removing the directive from them better communicates intent to future readers.
Takeaways
wire:navigatenow only intercepts same-origin HTTP(S) page visits; everything else falls back to the browser.- External URLs,
targetvalues other than_self,downloadlinks, andmailto:/tel:schemes are never prevented or prefetched. - No configuration changes are required — normal same-origin navigation behaves exactly as before.
- Audit any
livewire:navigateevent listeners and tests that relied on intercepting external or new-tab links.