Catch boot/chunk-load failures instead of leaving a blank login page#11714
Merged
Conversation
The white-screen-on-login reports are boot-level failures that happen before React renders the route tree, so the useLoginRequired spinner fix can't catch them and the page goes fully blank with no recovery. Two gaps made any such failure unrecoverable: use(bootstrapPromise) had no error boundary, and route lazy() imports weren't wrapped for stale-bundle reload (so a chunk that 404s after a deploy — "cacheless refresh fixes it" — blanked with no auto-reload). - BootErrorBoundary wraps the app mount: on a caught boot/render error it reports to ErrorReporting, reloads onto the fresh bundle when the deployed entrypoint changed (reloadOnAppEntrypointHeadersMismatch, guarded by a sessionStorage timestamp so it can't loop), and otherwise renders a dependency-free "couldn't finish loading — reload" fallback instead of a blank page. The copy is hard-coded English on purpose: i18n may be exactly what failed to load. - withEntrypointReloadOnLazy wraps every route's lazy import with checkAppEntrypointHeadersOnError, so a stale-deploy chunk 404 reloads onto the fresh bundle instead of surfacing as an unrecoverable blank. This turns the whole class of login blanks — stale bundle, adblocker, odd state — into a silent auto-reload or a visible, reportable error. It doesn't pinpoint each trigger (the adblocker case still wants a HAR), but it makes them non-fatal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Code Coverage Report: Only Changed Files listed
Minimum allowed coverage is |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Follow-up to the login work in #11693 — and a response to the round of Summer Larpin' "blank page on login" reports.
After a lot of digging this round, those blanks turned out to be boot-level failures that happen before React renders the route tree — so the
useLoginRequiredspinner fix (#11693, login-required routes) can't catch them, and the page goes fully blank with no recovery. I couldn't reproduce them in a clean browser (clean prod renders fine, even with Sentry/Rollbar/fonts blocked), which is itself the tell: they need real-user conditions — most commonly a stale deploy ("I did a lot of cacheless refreshing and it eventually worked" is the textbook signature), and for some users an adblocker or odd cache/session state. They're also invisible in Sentry (the failing chunk can be the Sentry SDK itself; adblockers block ingest; boot crashes precede SDK init), so the fix has to degrade gracefully on its own rather than rely on telemetry.Two gaps made any such failure unrecoverable, and this fixes both:
use(bootstrapPromise)had no error boundary — a rejected bootstrap or any uncaught render error blanked the whole app.lazy: () => import(...)imports weren't wrapped for stale-bundle reload, so a chunk that 404s after a deploy blanked with no auto-reload.The goal isn't to pin down every individual trigger (the adblocker case still wants an on/off HAR) — it's to turn the whole class of login blanks into either a silent auto-reload (stale deploy) or a visible, reportable "reload" page.
Changes
💻 Engineer-facing
BootErrorBoundarywraps the app mount. On a caught boot/render error it reports toErrorReporting, callsreloadOnAppEntrypointHeadersMismatch()to reload onto a fresh bundle when the deployed entrypoint changed (guarded by asessionStoragetimestamp so it can't loop), and otherwise renders a dependency-free "couldn't finish loading — reload" fallback. Copy is hard-coded English on purpose — i18n may be exactly what failed to load.withEntrypointReloadOnLazywraps every route'slazyimport with the existingcheckAppEntrypointHeadersOnError, so a stale-deploy chunk 404 reloads onto the fresh bundle instead of blanking. Non-chunk route errors are unaffected.checkAppEntrypointHeadersMatch.tshelpers; no new reload machinery.Risks
Low. The boundary only renders on a thrown error; the lazy wrapping only changes behavior on a failed import (reload-if-stale, else reject as before). The reload-loop guard prevents repeated auto-reloads. Worst case is the fallback "reload" page instead of a blank — strictly better than today.
Testing
tsc --noEmit, eslint, and the existinguseLoginRequired/openidtests pass. NewBootErrorBoundary.test.tsxcovers rendering children normally and rendering the reload fallback (not a blank) when a child throws.Release plan and notes
🚢 — and this also makes future boot failures visible and reportable instead of silent white screens, so we stop flying blind. Still want an adblocker-on/off HAR to nail Brian's specific case as a follow-up.
🤖 Generated with Claude Code