Fix "404: NOT_FOUND" on refresh in a React/Vite app on Vercel
Updated · Applies to: Vercel, Vite, React Router, Lovable, Bolt, Cursor
TL;DR fix: Vercel looks for a real file at /dashboard and doesn't find one, because your single-page app only has index.html. Add vercel.json at the repository root with {"rewrites":[{"source":"/(.*)","destination":"/index.html"}]}, commit it, and redeploy. Do not add this to Next.js projects.
Steps
- Confirm it's an SPA routing issue. The home page loads and in-app navigation works, but refreshing or opening a URL like /dashboard directly shows 404: NOT_FOUND.
- Create vercel.json at the repository root. Add a catch-all rewrite to /index.html. If you have serverless functions under /api, exclude them from the rewrite.
- Commit the file. Make sure vercel.json is tracked by git and not in .gitignore. Vercel shows no error if the file is missing, so the rewrite just silently doesn't apply.
- Check the framework preset. In your Vercel project settings, the Framework Preset should be Vite. Vercel notes that a preset of Other can stop even a correct rewrite from taking effect.
- Redeploy and test a deep link. Open https://your-app.vercel.app/some/route directly and refresh it.
The file
vercel.json at the root of your repo:
{
"rewrites": [
{ "source": "/((?!api/).*)", "destination": "/index.html" }
]
}
The (?!api/) part keeps any /api/* serverless functions working. If you don’t have any, "/(.*)" is fine.
Vercel’s guide explains the cause: “Vercel resolves every URL as a server-side filesystem path by default… It is wrong for single-page applications (SPAs) built with Vite, Create React App, or similar tools.”
Prompt for your AI tool
Deep links 404 on Vercel (404: NOT_FOUND) in our Vite + React Router app. Add vercel.json at the repo
root with a catch-all rewrite to /index.html that excludes /api/, make sure it's committed, and confirm
the Vercel framework preset should be Vite. Don't change routing code. When to get help
Get help if you're on Next.js (where this rewrite is wrong), if API routes start returning HTML after the change, or if you see a blank white page instead of a 404.
Want us to look at your app?
VibeCPR Rescue Reports are launching soon: we scan your repo and live app, and a human verifies every critical finding. Join the waitlist for early access and founder pricing. You'll also get Patch Notes, our weekly fixes newsletter.
FAQ
Why do I get 404: NOT_FOUND on Vercel when I refresh my React app?
Vercel resolves each URL as a file path by default. A Vite or Create React App single-page app only has index.html, and React Router handles routes in the browser, so a direct request to /dashboard has no file to serve. Add a catch-all rewrite to /index.html in vercel.json.
Should I use routes or rewrites in vercel.json?
Use rewrites. Vercel's guidance for SPAs is a catch-all rewrite: { "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] }.
Does this apply to Netlify or Cloudflare Pages?
The cause is the same. On Netlify add a _redirects file with /* /index.html 200. Cloudflare Pages serves index.html for unknown paths automatically when there is no 404.html in the build output.
Sources
- Vercel KB: Why is my deployed project giving a 404?
- Vercel KB: How to debug 404 errors
- Netlify docs: rewrites for history pushstate
- Cloudflare Pages docs: single-page application rendering
Related fixes
- Fix "Your Cloud Firestore database has insecure rules" (Firebase email)
- Fix Stripe webhook "No signatures found matching the expected signature for payload"
- Fix Supabase Edge Function "blocked by CORS policy" (preflight)
- Fix "new row violates row-level security policy for table" (Supabase)
- Fix "Module not found: Can't resolve" on Vercel (works locally)