Vercel 404 NOT_FOUND on Page Refresh: How to Fix It
Your Vite or React app works until you refresh a page, then Vercel shows 404: NOT_FOUND. Here's why it happens and the one-file fix.
You deploy your app to Vercel. The home page opens. You click around the menu and every page works. Then you press refresh on /dashboard — or someone opens a link you sent them — and instead of your app they get a plain page that says 404: NOT_FOUND.
Nothing is wrong with your code. This is how single-page apps behave on any host until you tell the host one small thing. It takes one file to fix.
What the error looks like
The pattern is always the same:
- Your home page (
yourapp.com) loads fine. - Clicking links inside the app — to
/dashboard,/settings,/pricing— works fine. - Pressing refresh on one of those pages, or opening one of those addresses directly (from a bookmark, an email, a shared link), shows Vercel's own error page instead of your app.
Vercel's error page says 404: NOT_FOUND, with a line Code: NOT_FOUND and an ID: with a long reference. That page comes from Vercel, not from your app — your app never even started.
Why it happens
Apps built with Vite and React are usually single-page apps (SPAs). When you build one, the result is basically:
- one
index.htmlfile, - a folder of JavaScript and CSS files,
- your images and other static files.
There is no file called dashboard or pricing. Those "pages" exist only inside your JavaScript: a router (React Router, TanStack Router or similar) reads the address in the browser and draws the right screen.
When you click a link inside the app, the router changes the address and draws the new screen without asking the server for anything. That's why clicking works.
When you refresh, the browser asks Vercel for /dashboard. Vercel looks for a file at that path, finds none, and answers 404. Your index.html — the only file that knows how to show the dashboard — never gets loaded.
The fix is to tell Vercel: "for any address you don't have a file for, send index.html instead". Then the app loads, the router reads the address, and the right page appears.
Does this apply to your app?
It applies if your app is a client-side single-page app on Vercel. Common cases:
- Bolt projects built with Vite and React that you deployed to Vercel yourself.
- Lovable projects on React + Vite. According to Lovable's documentation, projects created from May 13, 2026 (June 22, 2026 on Enterprise plans) use TanStack Start with server-side rendering; older projects are React + Vite. If you exported an older Lovable project and deployed it to Vercel, this article is for you.
- Vite apps you built in Cursor or by hand with
npm create vite.
It usually does not apply to Next.js. Next.js apps on Vercel have their routes handled by Next.js itself, so a 404 there means the page really doesn't exist for that address (a missing file in the app folder, a dynamic route that doesn't match). That needs a different fix.
The fix: add vercel.json
This is the configuration from Vercel's guide for Vite ("Using Vite to make SPAs"):
- Open your project's code (in GitHub, in Cursor, or wherever your files live).
- In the root of the project — the same folder as
package.json— create a new file named exactlyvercel.json. - Paste this into it:
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
]
}
- Save and commit the file. If your project is connected to Git, Vercel starts a new deployment by itself. If not, deploy again the way you usually do.
- Wait until the deployment shows Ready in the Vercel dashboard.
- Open a private (incognito) window and go straight to a deep page, for example
https://yourapp.com/dashboard. Then press refresh. You should see your app, not a 404.
What this does: every request that doesn't match a real file is answered with index.html. Your JavaScript, CSS and images are real files, so they're still served as they are. Only "page" addresses go to index.html, and your router does the rest.
Already have a vercel.json? Don't create a second one. Add the rewrites part to the existing file, keeping what's there. If it already has a rewrites list, add the rule as the last item, so more specific rules (like an API proxy) still match first.
Prefer to ask your AI builder? Paste this:
My Vite + React app returns Vercel's 404: NOT_FOUND when I refresh any page except the home page.
Add a vercel.json in the project root with a rewrite that sends all paths to /index.html,
following Vercel's "Using Vite to make SPAs" guide. Don't change anything else.
Still getting 404?
Go through this list in order:
- The file isn't in the right folder. It must be in the folder Vercel builds from. Usually that's the repository root. But if your project's Root Directory setting in Vercel points to a subfolder (common in projects with a
frontend/orweb/folder),vercel.jsonmust be inside that subfolder. - The file name is wrong. It must be
vercel.json, all lowercase — notvercel.json.txt, notVercel.json. Some editors add.txtsilently. - It uses the old
routesformat. Older guides use"routes": [...]instead of"rewrites". Vercel's configuration docs sayroutescan't be combined withrewrites,redirects,headers,cleanUrlsortrailingSlash. If you see an error about this, replace theroutesblock with therewritesblock above. - You use
cleanUrls. Vercel's Vite guide notes that ifcleanUrlsis set totruein yourvercel.json, you should leave out the file extension in the source and destination — so/index.htmlbecomes/. - An old commit is deployed. In the Vercel dashboard, open Deployments and check that the latest production deployment is from the commit that added
vercel.json, and that it finished successfully. A failed build keeps the previous version online. - The Output Directory is wrong. Vite builds into
distby default. If the project's Output Directory setting points somewhere else, Vercel may not findindex.htmlat all. The framework preset "Vite" sets this correctly. - Your browser cached the 404. Test in a private window, or on your phone with Wi-Fi off.
- Every page is down, not just deep links. Then it's probably not routing at all. Check whether Vercel is having an incident on our live Vercel status page, and look at your latest deployment's logs.
If the home page itself is also broken — blank, or showing an error — that's a different problem. See why a Bolt.new app stops working after deploy and the blank white screen guide. A white screen with supabaseUrl is required in the console has its own fix: supabaseUrl is required after deploy.
Same problem on Netlify
Netlify has the same behavior and the same kind of fix. Netlify's docs on rewrites say that single-page apps that use clean URLs need a rewrite rule that sends every path to index.html with status 200.
The simplest way for a Vite app:
- In your project, create the file
public/_redirects(thepublicfolder is copied into the build as-is, so the file ends up next toindex.html). - Put this single line in it:
/* /index.html 200
- Deploy again.
If you use a netlify.toml file instead, the same rule looks like this:
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
If you have other redirect rules, Netlify's docs say this catch-all rule should usually come last.
How to catch it next time
This bug is sneaky because the page everyone checks — the home page — is always fine. Most people only find out when a customer says "your link doesn't work".
Two habits help:
- Test a deep link after every deploy. Open
/pricingor/dashboarddirectly in a private window, not by clicking from the home page. - Monitor an inner page, not just the home page. An uptime monitor that only watches
yourapp.comwill say "up" while every other address is a 404.
With Pingbuddy you can add an inner page as its own site — for example yourapp.com/pricing next to yourapp.com. The Free plan covers up to 20 sites, each checked every 5 minutes, and a 404 is reported as a failure. You get an alert on Telegram or by email after a second check confirms it, and another when it's fixed.
Want a quick look first? Run a free checkup of your site, or read how to check if your website is down for more ways to test.
FAQ
- Why does my Vercel site work from the home page but 404 on refresh?
- Your app is a single-page app: there is one index.html, and the app itself decides what to show for /dashboard or /pricing. Clicking a link works because the app handles it in the browser. A refresh asks Vercel for a file called /dashboard, which doesn't exist, so Vercel answers 404: NOT_FOUND. A rewrite in vercel.json fixes it.
- Where exactly does vercel.json go?
- In the root of your project — the same folder as package.json. If your project's Root Directory setting in Vercel points to a subfolder, put vercel.json in that subfolder.
- Will the rewrite break my images, CSS or JavaScript?
- No. Files that really exist in your build, like your JavaScript bundle and images, are still served as they are. Only addresses that don't match a file are sent to index.html, where your app's router takes over.
- Do I need this for a Next.js app?
- No. Next.js on Vercel handles its own routes. If a Next.js page returns 404 on refresh, the page really doesn't exist for that address, so check your app folder and dynamic routes instead.