Quick fix
# One-liner: fix the release order — assets first, entry last; keep old assets for at least one release cycle
# 1) upload the new dist/ entirely (incl. new hashed assets) 2) upload/switch index.html last
In the release script, move the index.html overwrite to the last step, and keep the old asset directory when uploading.
What you will run into
After publishing a new version, some users see a white screen, and a refresh sometimes recovers; the console shows ChunkLoadError: Loading chunk N failed;
the hashed resource requested by a dynamic import returns 404. You visit it yourself and it's fine — because your cache is fresh.
Why this happens
Version skew: the browser's cached old entry HTML still references old-version hashed chunks, while the server's new release already deleted the old resources.
When the user triggers a dynamic import, the old chunk 404s → module load fails → whole page white.
| Role | State |
|---|---|
| User's browser | Caches old entry HTML pointing at old hashed resources |
| Server | Deployed the new version; old resources deleted |
| Result | Old chunk 404 → ChunkLoadError → white screen |
Vite-built chunk filenames carry content hashes (measured: feature-DP5yDwy7.js → feature-PcJGFbac.js); the hash changes with each version, and old/new HTML reference completely different resources.
Vite build output is "entry HTML + content-hashed chunks": the hash is derived from file contents, so content changes change the hash.
The browser first fetches the entry HTML, then loads chunks at runtime per the references inside it — a two-step fetch with a time gap, during which the server may have deployed a new version and deleted the old chunks.
A cached old HTML points at old hashes; the server no longer has those files → dynamic import 404 → ChunkLoadError. Deterministic takeaway: this is not a code bug, it is version skew between the "client's reference set" and the "server's asset set".
Three fixes: keep old chunks at deploy time (don't delete old dist files / use versioned directories), make the entry HTML non-cacheable (Cache-Control: no-cache while chunks stay long-cacheable), or force a refresh after release.
Diagnosis: in the Network panel the failed URL carries an old hash with no matching file in the server dist.
Step by step
1. Release order: assets first, entry last — Required
Ship hashed static assets first, switch the entry HTML last. Both old and new entries can then find their own resources.
2. Keep old assets for a transition period — Required
Keep old assets for at least one release cycle (especially with back-to-back releases) so users still holding the old entry transition smoothly.
3. nginx fallback must not swallow static-asset 404s — Required
try_files should match real files first, then fall back to index.html — not a blanket fallback:
location / {
try_files $uri $uri/ /index.html; # real files first, then fallback
}
4. CDN and Service Worker follow the same version-consistency rules — Required
Design CDN cache policy and SW update logic as "assets before entry" too; don't let a caching layer amplify version skew.
How to confirm it works
Simulate an old user with an old-version entry (expected shape):
curl -H "Cache-Control: no-cache" http://your-domain/feature-PcJGFbac.js
→ HTTP 200 (new hashed asset exists)
curl -I http://your-domain/index.html
→ HTTP 200, referenced asset list consistent with live resources
After release both old and new pages return HTTP 200 (measured: keeping old assets → both 200); consecutive releases no longer produce ChunkLoadError.
Other environments
| Environment | Notes |
|---|---|
| Single release | Assets-first-then-entry solves it |
| Consecutive releases | Keep old assets for at least one release cycle, or the previous round's users still white-screen |
| nginx SPA fallback | try_files $uri first, then fallback; don't swallow static-asset 404s |
| CDN / Service Worker | Same version-consistency rules as the server |
| Already-white-screen users | Hard refresh or reopen recovers (entry becomes fresh after cache refresh) |
Symptom
After release, some users get a blank screen that occasionally recovers on refresh; console reports ChunkLoadError: Loading chunk N failed; hashed resources requested by dynamic import return 404.
Cause
Version mismatch: the browser-cached old entry HTML still references old-version hashed chunks, while the new release has already deleted the old resources -> dynamic import 404 -> module load fails -> full-page blank screen.
Solution
- Release order: assets first, entry last (hashed static assets go live first, entry HTML switches last).
- Keep old assets during a transition period (cover at least one release cycle).
- nginx SPA fallback must not swallow static-asset 404s (try_files matches real files first, then falls back to index.html).
- CDN and Service Worker follow the same version-consistency rules.
Verification Evidence
- Verified locally (2026-08-29): two builds of vite 5.4.21, chunk hash changed (feature-DP5yDwy7.js -> feature-PcJGFbac.js); old chunk HTTP 404 when the server only deployed v2 assets (reproduced); keeping old assets gives both old and new pages HTTP 200 (solution holds).
Evidence details kept locally (2026-08-29)
Applicable tools (2)
This problem is linked from the tool pages below — reachable from either side.
vite-deploynginxBundled solutions (combos)
This problem is part of the following verified bundles.
- Combo Draft: Web Deploy Verification verified
Source & evidence
← Build & Deploy domain | Problem Catalog Experience | Facing another issue? Go to the Helpdesk