Why Refreshing Your React Page Gives a 404 (and How to Fix It on Any Host)
Your single-page app works when you click around but shows 404 Not Found when you refresh or share a link. Why client-side routing causes it, and the exact fix for Nginx, Caddy, Netlify, Vercel, GitHub Pages and Express.
Your app works perfectly while you click around. Then you refresh /dashboard — or someone opens a link you shared — and get 404 Not Found. The home page still works. This affects nearly every React, Vue or Vite single-page app on its first deploy.
Why it happens
A single-page app (SPA) has one real HTML file: index.html. When you click a link inside the app, the router (React Router, for example) changes the URL and swaps the content in the browser. No new page is requested from the server.
But when you refresh /dashboard or open it directly, the browser asks the server for /dashboard. The server looks for a file or folder called dashboard, finds nothing, and returns 404. The router never gets a chance to run.
The fix, in one sentence
Configure the server to return index.html for any path that isn't a real file. The app loads, the router reads the URL, and shows the right page.
This is called a fallback or rewrite. How you set it up depends on where you host.
Nginx
location / {
try_files $uri $uri/ /index.html;
}
"Try the exact file, then a folder, otherwise serve index.html." (More on Nginx: reverse proxies explained.)
Caddy
example.com {
root * /srv/app/dist
try_files {path} /index.html
file_server
}
Netlify
Create public/_redirects (so it ends up in your build output):
/* /index.html 200
The 200 makes it a rewrite (the URL stays the same) rather than a redirect.
Vercel
For a plain Vite/React app, add vercel.json:
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
(Next.js apps don't need this — Next.js handles routes on the server.)
GitHub Pages
GitHub Pages has no rewrite setting. The common workarounds:
- copy
index.htmlto404.htmlduring your build, so GitHub's 404 page is your app; or - use
HashRouter, so URLs look like/#/dashboardand the server only ever sees/.
Both work; neither is as clean as a real rewrite. If you outgrow it, move to a host that supports rewrites.
Express (Node.js)
If you serve the built app from your own Node server, add the fallback after your API routes and static files:
app.use(express.static("dist"));
app.get("/api/books", ...); // API routes first
app.get("*splat", (req, res) => {
res.sendFile(path.resolve("dist", "index.html"));
});
(In Express 5 the catch-all is written *splat; in Express 4 it was "*".)
Don't break your API or real 404s
Two things to watch:
- API routes must come first. If the fallback catches
/api/...too, your API calls get HTML back and fail with confusing JSON errors. - Real "not found" pages. With a fallback, the server never returns 404 —
/total-nonsenseloads the app. Add a catch-all route in your router that shows a "Page not found" screen.
Also check the router's base path
If the app lives under a sub-path like /my-app/, both the build tool and the router need to know:
<BrowserRouter basename="/my-app">
and base: "/my-app/" in vite.config.ts. Otherwise you'll get 404s or a blank page after deploy.
The summary
- SPAs have one real page; refreshing a deep URL asks the server for a file that doesn't exist.
- Fix: serve
index.htmlfor any path that isn't a real file (a rewrite or fallback). - Keep API routes ahead of the fallback, and add a "not found" route in the app.
- Set the base path in both the build tool and router when hosting under a sub-path.
EasySpawn serves your app through a reverse proxy on your own domain with automatic SSL — and Claude Code can configure the fallback, deploy, and refresh a deep link to check it works. See how it works or join the waitlist.
Related: Anatomy of a URL · HTTP Status Codes Explained · What Is Vite? · Static vs Dynamic Websites
Keep reading
React App Shows a Blank Page After Deploying? Here's How to Fix It
Works locally, white screen in production. The usual causes of a blank page after deploying a React or Vite app — wrong base path, missing environment variables, a JavaScript crash, routing, caching — and how to diagnose each in two minutes with DevTools.
Core Web Vitals Explained: LCP, INP, and CLS for Beginners
Google's Core Web Vitals measure how fast, responsive and stable your pages feel. What LCP, INP and CLS mean, the thresholds for "good", how to check your scores, the difference between lab and field data, and the usual fixes.