Build & Deploy · recorded 2026-08-29 · applies to 2 tool(s) ✓ Verified

Problem: Refreshing a deep link white-screens after deploy (404) — SPA history fallback missing —— Trove problem library

Verified 2026-08-29history-route refresh 404 — nginx try_files falls back to the SPA entry (/app/index.html); static assets get their own location with =404.

Quick fix

# One-liner: core nginx fix — fall back nonexistent paths to the SPA entry; expose static-asset 404s separately
# location /app/ { alias /path/to/dist/; try_files $uri $uri/ /app/index.html; }
# location /app/assets/ { alias /path/to/dist/assets/; try_files $uri =404; }

Adjust these two locations to your deploy directory, then nginx -t && nginx -s reload.

What you will run into

Local vite dev works fine; after building and deploying to a server, the homepage loads and in-site route navigation works, but manually refreshing a sub-route page (deep link) white-screens, and the deep-link request returns 404 in the console.

Never reproducible locally; the problem only shows after deploy.

Why this happens

With vue-router using createWebHistory (HTML5 History mode), URLs carry no #. On refresh, the browser bypasses the front-end router entirely and requests the sub-route path directly from the server — for example /app/docs/setup.

The server looks for a physical file at that path, does not find one, and returns 404, which renders as a white screen.

In-app navigation never hits the server, because the router intercepts it. That is why "navigation works, but refresh white-screens" is the strongest signature of this problem.

ScenarioBehavior
In-site navigation✅ Frontend router handles it; no server request
Refresh a deep link❌ Browser directly requests /app/docs/setup; server has no such file → 404
Direct homepage access✅ Physical file exists

vite dev has a built-in history fallback (every path falls back to index.html), so local dev always works and the bug appears only after deploy — the classic "dev and production static-server behavior differ" case.

Deterministic takeaway: the production server must fall back unknown paths to index.html — nginx try_files $uri $uri/ /index.html;, the SPA fallback option on static hosting platforms, or a Node server returning index.html for app.get('*', ...).

The fix is verified when refreshing a deep link returns 200 instead of 404.

Step by step

1. First diagnostic step: base consistency — Diagnose

vite.config's base, createWebHistory(base), and the server deploy directory must be exactly consistent (especially the trailing /). Partial asset 404s / missing icons are usually base inconsistency.

2. nginx history fallback (core fix) — Required

location /app/ {
  alias /path/to/dist/;
  try_files $uri $uri/ /app/index.html;
}

3. Static-asset 404s must not be swallowed by the fallback — Required

A blanket fallback returns 200 + HTML even for missing JS/CSS (harder to debug when dynamic imports fail; cross-confirms with ChunkLoadError). Give assets their own location:

location /app/assets/ {
  alias /path/to/dist/assets/;
  try_files $uri =404;
}

4. Apache equivalent — Required

.htaccess: RewriteEngine On + rewrite to /app/index.html when file/dir doesn't exist (same logic as step 2).

How to confirm it works

curl assertions for the three path classes (expected output):

curl -I http://your-domain/app/docs/setup
→ HTTP 200 (deep link falls back to the HTML entry)
curl -I http://your-domain/app/assets/main.js
→ HTTP 200 (real asset)
curl -I http://your-domain/app/assets/missing.js
→ HTTP 404 (missing asset correctly exposed, not swallowed)

Refreshing any deep link no longer white-screens (measured: after the fix, deep link 200, real asset 200, missing asset 404).

Other environments

EnvironmentNotes
nginx alias deployDeep-link fallback + assets own location (measured fix)
nginx root deploySame try_files $uri $uri/ /index.html; mind the base path
Apache.htaccess rewrite; same logic
base inconsistencyPartial asset 404 / missing icons; check base before fallback
Blanket fallbackMissing JS also returns 200 HTML; harder to debug (measured pitfall)

Symptom

Local vite dev works fine; after build & deploy, the homepage and in-app routing work, but manually refreshing a deep-linked sub-route goes blank — the network panel shows the deep link returning 404.

Cause

vue-router uses createWebHistory (HTML5 History mode), so URLs have no #. On refresh the browser requests the sub-route path (e.g. /app/docs/setup) directly from the server; the server has no such physical file and returns 404 → blank page. vite dev ships its own history fallback, so this never reproduces locally — it only shows up after deployment.

Solution

  1. base consistency (check first): base in vite.config, createWebHistory(base), and the server deployment directory must match exactly (watch the trailing /). Partial asset 404s / missing icons are usually a base mismatch.
  2. nginx history fallback (core fix): fall back non-existent paths to the SPA entry:
   location /app/ {
     alias /path/to/dist/;
     try_files $uri $uri/ /app/index.html;
   }
  1. Don't let the fallback swallow real static 404s (measured fix): a blanket fallback turns missing JS/CSS into 200 + HTML, which makes dynamic-import failures much harder to debug (cross-check the ChunkLoadError entry). Give assets their own location so real 404s stay visible:
   location /app/assets/ {
     alias /path/to/dist/assets/;
     try_files $uri =404;
   }
  1. Apache equivalent: RewriteEngine On + rewrite to /app/index.html when the file/directory doesn't exist (.htaccess, same logic as 2).
Verification Evidence
  • Local test (2026-08-29): nginx 1.28.3 + minimal SPA fixture (index.html + assets/), alias deploy + curl assertions.
  • Repro: without fallback GET /app/docs/setup → 404 (blank-refresh root cause confirmed), /app/index.html → 200.
  • Article fix: with try_files ... /app/index.html, deep link → 200 (HTML entry); but GET /app/assets/missing.js → 200 HTML — static 404 swallowed (a pitfall the article doesn't mention).
  • After the fix: deep link 200, real assets 200, missing assets 404 (properly exposed).
  • local repro log 2026-08-29

Applicable tools (2)

This problem is linked from the tool pages below — reachable from either side.

vite-deploynginx

Bundled solutions (combos)

This problem is part of the following verified bundles.

Source & evidence

Recorded Verified ✓ VerifiedApplies to 2 tool(s)
Entries contain locally measured or reviewable-source solutions; unmeasured items are explicitly labeled.

Source: https://juejin.cn/post/7485263262508138523

← Build & Deploy domain | Problem Catalog Experience | Facing another issue? Go to the Helpdesk