@robertschlackman I’m fairly confident I can explain this (Well, Claude can) it’s a CDN caching interaction rather than a Meteor bug, and the rspack upgrade is what made a long-dormant misconfiguration start biting.
What’s happening
Meteor serves the root HTML document without a Cache-Control header. That HTML is the entry point that references your JS/CSS bundles by their content hash (e.g. /abc123.js), and those hashes change on every deploy. The hashed assets themselves are served immutable, max-age=1y — which is correct, because their URL is unique per build.
The problem is the entry document. A text/html response with no cache header invites a CDN to apply its own heuristics, and if Cloudflare is configured to cache it (a “Cache Everything” page rule, or a Cache Rule that catches HTML), the edge keeps serving an old HTML pointing at old bundle hashes. After you deploy, the new origin no longer has those files, so:
- old HTML (from the edge) → requests
/<oldhash>.js → 404 → the main script never loads → white screen.
This lines up with everything you described:
- “Nothing in the console / error tracker” — a
<script> that 404s doesn’t throw a catchable JS exception; it fires an error event on the element that window.onerror and most error trackers don’t capture. So a silent console is expected here, not evidence against it.
- “Purging Cloudflare cache fixes it” — that evicts the stale HTML at the edge, so everyone gets the new entry document.
- “Clearing browser data fixes it for that user” — same fix one layer down: it drops the browser’s own heuristic copy of the header-less HTML (plus the
autoupdate version Meteor persists in localStorage), forcing a fresh fetch. (Side note on “clearing minimongo”: vanilla minimongo is in-memory and can’t persist across a reload, so the artifact actually being cleared is the cached HTML/bundle + that localStorage version — same root cause, the wording just points elsewhere.)
And critically: when the entry bundle 404s, the app never boots far enough to open a DDP connection, so Meteor’s own hot-code-push/autoupdate can’t kick in to self-heal. The user is stuck until the cache expires or is cleared.
Why rspack made it worse
Pre-rspack your app shipped largely as one bundle, so a stale entry failed in exactly one way. rspack splits every import() boundary into its own content-hashed chunk, deletes the previous build’s chunks on each deploy (its clean step — no grace window where old and new coexist), and ships no retry/reload on a failed chunk load. So now there are many more independently-versioned files that can 404, and zero tolerance for a stale reference. A client running an old bundle that lazy-loads a route then hits a ChunkLoadError (the chunk hash it wants was deleted), and the route renders blank. The upgrade itself was the trigger because it was a single deploy that rotated every hash at once.
How to confirm (2 minutes)
Next time it happens, or just on a normal load:
- DevTools → Network. Reload. Look for any
.js/.css returning 404. If you see one, that’s it.
- Click the document request for
/ → Response Headers. Look for cf-cache-status: HIT and whether there’s a Cache-Control. If Cloudflare is serving the HTML from cache, that’s the smoking gun.
The fix
It’s on the Cloudflare side:
- Never edge-cache the HTML document. Find the “Cache Everything” page rule (or Cache Rule) and exclude
text/html / your app routes from it. Cloudflare’s default already bypasses HTML — so if you’re hit by this, something is explicitly caching it.
- Keep caching the hashed assets aggressively — they’re already
immutable and safe.
Optional belt-and-suspenders for the rspack chunk case: add a global handler that catches ChunkLoadError / a failed dynamic import() and does a single window.location.reload().
There’s also a reasonable argument that Meteor should send Cache-Control: no-cache on the boilerplate HTML by default instead of trusting every CDN to be smart — happy to raise that with the core team if others run into this.
Let me know what the Network tab shows after a repro — if it’s a 404 on the entry bundle, the Cloudflare rule is the whole story. Cc @italojs