livewire #10517: Backport navigate rejection fix to 4.x
Contributed by calebporzio
Part of the livewire v4.4.7 release
Categories: fix (3), upgrade notes (11)
Livewire 4.x now includes a backport of #10491 (landed via #10517) that changes how wire:navigate handles failed requests and failed prefetches. Two problems are addressed at once: failed navigate requests and speculative prefetches used to escape as unhandled promise rejections — noise that every error-tracking SDK (Sentry, Bugsnag, Datadog) dutifully files as an application error — and a failed navigation used to die quietly, hiding the progress bar and leaving the user stranded on the current page. With this fix, rejections are observed and handled, and when the browser reports it is offline, Livewire hands the navigation back to the browser so the user lands on the browser's own offline page. For anyone upgrading from v3.8.10, this requires no code, config, or API changes; it is purely a runtime behavior improvement inside the navigate plugin.
What changed
Three files in the navigate plugin's JavaScript:
js/plugins/navigate/fetch.js—performFetch()now attaches a deliberate no-op rejection handler:sendNavigateRequest(uri, callback, errorCallback).catch(() => {}). By the time the promise rejects,errorCallbackhas already dealt with the failure, so leaving the promise unobserved accomplished nothing except producing an "Uncaught (in promise)" entry in the console and in your error tracker.js/plugins/navigate/index.js— the navigate error callback now inspects why the fetch failed instead of only finishing the progress bar. A cancellation Livewire initiated itself (AbortError) is ignored, because the user is already on their way somewhere else. A failure while the browser reports being online is also ignored — following a redirect to another origin fails the same way a dead network does, and staying put is intentional there. Only whennavigator.onLineisfalsedoes the plugin fall back towindow.location.href = destination.href, triggering a hard browser navigation.js/plugins/navigate/prefetch.js— each prefetch entry now carries awhenFailedcallback. When a prefetch fails, its cache entry is deleted so it will be attempted again, andwhenFailed()runs. When a navigation claims an in-flight prefetch, it registers its own fallback aswhenFailed, so a failing prefetch hands the navigation down the normal request path instead of leaving the user hanging.
Why it matters
Before this fix, three failure modes produced poor outcomes:
- Error-tracker noise. Hovering links triggers prefetches; on flaky connections many of them fail. Each failure surfaced as an unhandled rejection and was reported by monitoring SDKs as a genuine application error, burying real issues.
- Dead clicks. A failed navigate request only hid the progress bar. The user clicked a link and nothing happened — no navigation, no feedback, no fallback.
- Hanging navigations. If a click resolved to an in-flight prefetch and that prefetch then failed, the pending navigation had nowhere to go, stranding the user mid-click.
The new failure path
The decision tree in the navigate error callback now reads (simplified from the diff):
if (requestWasCancelled(error)) return // we aborted it ourselves; user already moved on
if (navigator.onLine) return // online failure (e.g. cross-origin redirect): stay put
window.location.href = destination.href // offline: let the browser show its native offline page
From the developer's perspective, the markup is unchanged:
<a href="/reports" wire:navigate>View reports</a>
If the device is offline and /reports cannot be fetched, the browser now performs a full page load to /reports and displays its own offline error page — exactly what the same click would do in an app without wire:navigate. While online, a failed request stays a no-op on the page, but no longer leaks a rejection into the console.
Upgrade impact
Coming from v3.8.10 into the 4.x line, there are no migration steps for this change: it is contained to the compiled navigate plugin's JavaScript. Things worth knowing:
- Fewer false positives in monitoring. Expect "Failed to fetch"-style unhandled rejections originating from
wire:navigateand prefetching to disappear. If you added globalunhandledrejectionignore rules specifically to silence them, you can tighten those filters. - A visible behavior change when offline. Tests or UX assumptions that relied on a failed navigate click doing nothing will now observe a full page load. This is intentional and matches the fallback behavior of a traditional multi-page app.
- Coverage. Four new Dusk browser tests verify that a failed prefetch logs no console errors, that a failed prefetch never navigates anywhere on its own, that an offline navigate failure falls back to a hard navigation, and that an in-flight prefetch failure does the same.
The backport was validated on the combined release-prep state: 39 focused PHP tests with 71 assertions, 145 JavaScript tests passing (1 skipped), and a clean npm run build.
Takeaways
- Failed
wire:navigaterequests and prefetches no longer escape as unhandled promise rejections, keeping error trackers clean. - When the browser is offline, a failed navigation falls back to
window.location.href, surfacing the browser's native offline page instead of a silent dead click. - Self-cancelled requests (
AbortError) and failures while online remain intentional no-ops, preserving behavior for cross-origin redirects. - In-flight prefetch failures now hand pending navigations to the normal request path, so users are never left hanging.
- No API, config, or markup changes are required; the fix ships as part of upgrading from v3.8.10 to 4.x.