Deploying React AppsEntry 9 of 11
Server Rewrites for Client-Side Routing
A server rule that returns index.html for every URL that isn't a real file, so the client-side router can handle it.
Notes
- In an SPA,
/events/123doesn't exist as a file on the server. Clicking a<Link>works because React Router handles it in the browser. - But refreshing or opening
/events/123directly sends that path to the server, which returns 404. - The fix: rewrite all unknown paths to
/index.html. The app loads, and the router reads the URL and renders the right page. - Firebase's "Configure as a single-page app" option adds this automatically.
- Real files (JS, CSS, images) are still served normally because rewrites only apply when no file matches.
What / Why / When
- What
A catch-all fallback to
index.html.- Why
Deep links and refreshes work.
- When
Every SPA using browser history routing (React Router's
createBrowserRouter, Angular's default router).
Config examples
// firebase.json{ "hosting": { "public": "dist", "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], "rewrites": [{ "source": "**", "destination": "/index.html" }] }}
# Netlify: public/_redirects/* /index.html 200
// vercel.json{ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] }
# Nginxlocation / { try_files $uri $uri/ /index.html;}Angular
Identical requirement and identical configs. Only the public directory changes (dist/<project>/browser).
Differences and gotchas
- Hash routing (
createHashRouter, Angular'swithHashLocation()) avoids rewrites (/#/events/123) but produces uglier URLs. Use it only when you can't configure the server.
Quick reference
| Host | Rewrite |
|---|---|
| Firebase | "rewrites": [{ "source": "**", "destination": "/index.html" }] |
| Netlify | /* /index.html 200 |
| Vercel | rewrites in vercel.json |
| Nginx | try_files $uri $uri/ /index.html; |