Page works until I refresh, then 404 (React Router / SPA on static hosting)
404: NOT_FOUND when refreshing /dashboard or opening a direct link
In short: Your single-page app handles routes in the browser, but the host looks for a real file at /dashboard and doesn't find one.
Why it happens
Client-side routers (React Router, etc.) only work after index.html loads. On a hard refresh, the static host gets a request for /dashboard, has no such file, and returns 404 unless told to fall back to index.html.
How to fix it
- Vercel: add a rewrite in
vercel.jsonsending all paths to/index.html. - Netlify: add
public/_redirectscontaining/* /index.html 200. - Cloudflare Pages: if there's no top-level
404.html, Pages serves the app as an SPA automatically. Remove a stray404.htmlif routes 404. - GitHub Pages: no rewrites. Use hash routing or a
404.htmlthat redirects. - Make sure real API routes or static files aren't swallowed by the catch-all.
Example
// vercel.json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
Copy-paste fix prompt
I'm getting this error in my app: 404: NOT_FOUND when refreshing /dashboard or opening a direct link [Paste the full error, stack trace, and the file it points to here] Likely cause: Your single-page app handles routes in the browser, but the host looks for a real file at /dashboard and doesn't find one. Task: Identify my host and router, then add the exact SPA fallback config file for that host without breaking API routes or static assets. Rules: - First explain the root cause in 2 sentences and list the files you'll change. - Make the smallest change that fixes it. Don't refactor unrelated code. - Don't disable security or checks (RLS, auth, signature verification, type checking, CORS allow-all) to make the error disappear. - Never print or hard-code secret keys. - Finish by telling me exactly how to verify the fix.
Common with: Lovable, Bolt, Cursor. Last reviewed 2026-11-13.