Skip to content

fix: never blank the app when a deploy strands an open tab - #97

Merged
Jolah1 merged 1 commit into
mainfrom
fix/stale-chunk-blank-page
Jul 18, 2026
Merged

fix: never blank the app when a deploy strands an open tab#97
Jolah1 merged 1 commit into
mainfrom
fix/stale-chunk-blank-page

Conversation

@Jolah1

@Jolah1 Jolah1 commented Jul 18, 2026

Copy link
Copy Markdown
Owner

This is what made the facilitator dashboard show blank. Not a broken build: production and local both render it fine in a clean browser. The problem was an already-open tab.

Every view but LearnerView is lazy loaded. A tab open across a deploy asks Vercel for chunk filenames from the old build, which no longer exist there. The dynamic import rejects, and with no error boundary anywhere in the app, React unmounted the whole tree.

Reproduced by loading the built app, removing the facilitator chunk to stand in for a deploy, then navigating to it:

blank? YES
TypeError: Failed to fetch dynamically imported module:
  /assets/FacilitatorDashboard-BC0Lt40f.js

The fix

A boundary wraps the screens. A stale-chunk error reloads once, which is the actual fix, and anything else shows a panel explaining what happened with a reload button. A message always beats a blank page.

The auto-reload is guarded by a sessionStorage flag so a genuinely broken deploy cannot put the tab in a reload loop, which would be worse than the blank page since the user could not even read the error. The flag clears 30 seconds after a successful mount rather than on mount: a crash loop fails long before that, so the guard survives to show the panel, while a tab that recovered regains its retry for the next deploy.

Service worker bug found alongside

/assets/* cached any response, including the index.html that the SPA rewrite returns for a missing chunk. That would pin an HTML body under a .js URL for the life of the cache, so the failure would outlive the reload meant to fix it. Only successful responses are cached now, in both handlers, and VERSION rotates to v3 since the caching logic changed.

Verification

Same repro, both paths:

scenario result
fresh tab, chunk missing no blank, auto-reloads and recovers
retry already spent no blank, panel explains and offers reload

Screenshotting the panel caught a second bug of my own: the button rendered white-on-cream because I had used var(--accent) and var(--fg), neither of which exists in this theme. The accent is var(--bitcoin). Fixed there and in the review archive added in #96.

Full suite green: 30 unit, 30 integration, all nine e2e files, tsc --noEmit clean.

🤖 Generated with Claude Code

Every view but the learner is lazy loaded, so a tab left open across a
deploy asks for chunk filenames the server no longer has. The dynamic
import rejects, and with no error boundary anywhere React unmounted the
whole tree. The user got a white page with nothing saying that a reload
fixes it. A facilitator hitting that mid-workshop has no way to know.

Reproduced by loading the built app, removing the facilitator chunk to
stand in for a deploy, then navigating to it:

    blank? YES
    TypeError: Failed to fetch dynamically imported module:
      /assets/FacilitatorDashboard-BC0Lt40f.js

A boundary now wraps the screens. A stale-chunk error reloads once,
which is the actual fix, and anything else shows a panel explaining what
happened with a reload button, because a message always beats a blank
page.

The automatic reload is guarded by a sessionStorage flag so a genuinely
broken deploy cannot put the tab in a reload loop, which would be worse
than the blank page since the user could not even read the error. The
flag clears 30 seconds after a successful mount rather than on mount: a
crash loop fails long before that, so the guard survives to show the
panel, while a tab that recovered regains its retry for the next deploy.

Also fixed in the service worker, which cached any response for
/assets/*, including the index.html that the SPA rewrite returns for a
missing chunk. That would pin an HTML body under a .js URL for the life
of the cache, so the failure would outlive the reload meant to fix it.
Only successful responses are cached now, in both handlers, and VERSION
rotates to v3 since the caching logic changed.

Verified against the same repro, both paths: a fresh tab auto-reloads
and recovers, and a tab whose retry is spent shows the panel. Neither
goes blank. Screenshotted the panel, which caught the button rendering
white-on-cream because I had used var(--accent) and var(--fg), neither
of which exists in this theme; the accent is var(--bitcoin). Fixed there
and in the review archive added earlier.

Full suite green: 30 unit, 30 integration, e2e all nine files, tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
bitpilot Ready Ready Preview, Comment Jul 18, 2026 5:44pm

@Jolah1
Jolah1 merged commit 8ce97d0 into main Jul 18, 2026
5 checks passed
@Jolah1
Jolah1 deleted the fix/stale-chunk-blank-page branch July 18, 2026 17:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant