Skip to content

Bug 2044976 - Fix react suspense lazy load on deploy#9633

Open
camd wants to merge 1 commit into
masterfrom
camd/fix-lazy-load-cache-on-deploy
Open

Bug 2044976 - Fix react suspense lazy load on deploy#9633
camd wants to merge 1 commit into
masterfrom
camd/fix-lazy-load-cache-on-deploy

Conversation

@camd

@camd camd commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

Bug 2044976 - Recover from stale lazy-loaded chunks instead of spinning forever

The log viewer (and occasionally other views) would sometimes never load for a user — an infinite loading spinner — until they cleared their cache or did a hard refresh. A normal reload did not fix it.

Root cause

Each top-level app is code-split via lazy(() => import(...)) in ui/App.jsx, wrapped only in <Suspense>. This helps with load times immensely when someone loads /jobs they don't pay the import price for perfherder, and vise-versa.

Note

Chunks here are chunks of Treeherder deployed code, not test-chunking of jobs/tasks.

<Suspense> handles the pending state of a dynamic import, but it does not catch a rejected one. With no error boundary above it, a chunk that fails to load left React with nothing to catch — and the user stuck on the spinner.

A chunk load fails in two ways that both match the report:

  1. Deploy removes the chunk. Bundles are content-hashed and old ones are deleted on each deploy (clean: true). Treeherder is an SPA people keep open for hours, so a tab still running the pre-deploy runtime requests an old chunk URL that no longer exists → ChunkLoadError. The app shell HTML is no-store (verified in prod), so a reload always pulls current assets — but nothing was triggering that reload.
  2. Corrupt/stale immutable cached asset. Hashed assets are served Cache-Control: …immutable, so the browser never revalidates them on a normal reload — it keeps serving a bad cached copy. Only a cache-bypassing load replaces it. This matches Henrik's and Julian's description: reload doesn't help, "Disable Cache" fixes it permanently, intermittent, single-profile only.

Note

Right now I believe that "always Wd3" is incidental: that log content renders through the /logviewer lazy route, so that's the chunk he kept hitting. My suspicion was that it was one or more tabs that hadn't yet been reloaded after a deploy. After this fix is deployed, we can reassess if there is another issue.

Fix

Add a ChunkErrorBoundary around the lazy routes:

  • On a chunk-load failure it triggers a one-time reload to fetch the current assets, recovering silently in the common (post-deploy) case.
  • A 10-second cooldown prevents a reload loop when a reload can't fix it (the corrupt-immutable-cache case); instead it shows a readable message prompting a hard refresh.
  • Ordinary (non-chunk) render errors get a generic fallback instead of an unhandled crash.

Caching behavior is intentionally unchanged — immutable hashing is correct and stays. This change only ensures a failed chunk load becomes recoverable rather than a permanent stuck spinner.

Notes / possible follow-ups

This is a reactive fix (recovers after a failure). A proactive "a new version is available — reload" banner (poll a version hash, offer a manual reload) could further reduce occurrences; intentionally left out of scope here. But we may keep that as a future option.

@camd camd self-assigned this Jun 20, 2026
@camd camd requested a review from Archaeopteryx June 20, 2026 14:16
@camd camd changed the title Fix react suspense lazy load on deploy Bug 2044976 - Fix react suspense lazy load on deploy Jun 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant