Your app shows a 404 when you refresh the page: the fix for Vercel, Netlify, AWS and Nginx
Why deep links break on single-page apps, and the one rewrite rule each host needs, plus the two side effects to handle.
By Sameem Amjad
The short answer
Your app is a single-page app. The server has one real page, index.html, and the app draws every other URL in the browser. Clicking a link works because the browser never asks the server. Refreshing /dashboard does ask the server, which looks for a file called dashboard, finds nothing and returns a 404. The fix is a rewrite that sends every path that isn't a real file to index.html.
Which apps this applies to
Any app rendered entirely in the browser: Vite and React with React Router, Create React App, and the Vite apps AI builders generate. Lovable projects created before it moved new projects to TanStack Start in 2026 are this kind of app, as are many Bolt projects. Frameworks that render pages on a server, such as Next.js, route on the server and don't have this problem. Newer Lovable projects run on TanStack Start, which also renders on a server: if one of those 404s after deploying, the cause is the deploy target rather than a missing rewrite, and this fix won't apply.
Vercel
Add a vercel.json at the root of the project:
{
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
]
}Vercel serves real files first, so your JavaScript, CSS and images still load; the rewrite only applies when no file matches. If the project has serverless functions under /api, leave them out of the rewrite by using /((?!api/).*) as the source.
Netlify
Add a _redirects file to the folder that gets published (for a Vite app, put it in public/):
/* /index.html 200The 200 makes it a rewrite rather than a redirect, so the address bar keeps the URL the visitor asked for. Netlify also serves real files before applying the rule.
AWS: S3 with CloudFront, or Amplify
With S3 behind CloudFront, add custom error responses to the distribution for both 403 and 404 that return /index.html with a 200 status. You need both codes: S3 answers 403, not 404, for a missing file unless the requester has s3:ListBucket permission, which CloudFront's origin access and most public-read bucket policies don't grant.
On AWS Amplify Hosting, add the single-page-app rewrite from Amplify's documentation under Rewrites and redirects. It sends everything to /index.html as a 200 rewrite except files with the extensions it lists, and Amplify doesn't check whether a file exists first, so add any other extension your build serves (for example xml, webmanifest, jpeg, avif or pdf) or those files will come back as your app.
Nginx
location / {
try_files $uri $uri/ /index.html;
}try_files serves the file if it exists, then a directory, and otherwise falls back to index.html.
Two side effects to handle
- Real 404s disappear. Every mistyped URL now returns your app with a 200 status, which search engines treat as a soft 404. Add a catch-all route in the app that shows a proper not-found page, and mark it
noindex. - Search engines still see a blank page. The rewrite fixes refreshes, not SEO: a crawler that doesn't run JavaScript gets the same empty
index.htmlfor every URL. If the app needs to rank, prerender each route to real HTML at build time. This site is a Vite and React app prerendered page by page, which is how every page here reaches search engines with its own title and content.
If the 404 is the least of it
A deploy that 404s is often the first sign of a wider production problem: environment variables missing on the host, login redirects pointing at the preview URL, or a database that's open to the public. If you'd like the whole app checked and fixed, that's what my app rescue service does.