404 on page refresh

Your single-page app works from the home page, but the host has no file for deeper URLs, so reloads and shared links hit a 404.

Browser page
404: NOT_FOUND This page doesn’t exist It may have been moved, removed, or never existed. 404 NOT_FOUND sfo1::ntg68-1793091406762-e6fdeff1eb27

Also appears as

  • 404: NOT_FOUND Code: NOT_FOUND ID: sfo1::ntg68-1793091406762-e6fdeff1eb27
  • Page not found Looks like you’ve followed a broken link or entered a URL that doesn’t exist on this site.

Prompt to fix it

My deployed app works from the home page, but reloading or opening a link like /dashboard directly shows "404: NOT_FOUND" (or Netlify’s "Page not found"). It’s a single-page app with client-side routing. Work out which host we deploy to from the repo and add the right fallback: a vercel.json rewrite to /index.html for Vercel, public/_redirects with "/* /index.html 200" for Netlify, or not_found_handling: "single-page-application" for Cloudflare Workers. Keep real files under /assets served normally, and add a React Router catch-all route for unknown paths. Don’t switch to HashRouter.

Paste into Layerwise or any AI coding tool.

What it means

Apps built with Vite and React Router are single-page apps: the server has one real page, index.html, and JavaScript draws /dashboard or /settings in the browser. Clicking a link works because the router changes the URL without asking the server. A reload or a shared link sends /dashboard straight to the host, which looks for a file by that name, finds none, and returns 404.

The fix is a rewrite: a host rule that answers unknown paths with index.html so the router can take over. Vercel, Netlify, and Cloudflare check for real files first, so your JavaScript, CSS, and images keep loading normally.

It works on your machine because Vite’s dev server and vite preview already fall back to index.html. That’s why the bug only appears after you deploy.

Common causes

  • The app deploys to Vercel without a vercel.json rewrite, so only / maps to a file.
  • A Netlify site has no _redirects file in the published folder, often because it sits in the project root instead of public/.
  • A custom server or Nginx config serves static files without a fallback to index.html.
  • The app is on GitHub Pages, which has no rewrite rules, so deep links get its 404 page.

How to fix it

  1. On Vercel, add a vercel.json at the project root that rewrites /(.*) to /index.html, then redeploy.
  2. On Netlify, create public/_redirects with the line /* /index.html 200. The 200 makes it a rewrite, so the URL stays the same.
  3. On Cloudflare Workers, set not_found_handling to "single-page-application" in the assets section of your Wrangler config. Cloudflare Pages does this on its own when the build has no top-level 404.html.
  4. Add a catch-all route (path="*") in React Router so unknown URLs show your own not-found page.
  5. Test by opening a deep link like /dashboard in a new tab on the deployed site.

Example fix

{
  "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
All errors
Failed to load module script: Expected a JavaScript-or-Wasm module script but the server responded with a MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.

Failed to load module script

The browser asked for your app’s JavaScript and got an HTML page back, so it refused to run it and the page stayed blank.

useNavigate() may be used only in the context of a <Router> component.

useNavigate() may be used only in the context of a <Router>

A component used React Router’s navigation outside the router, so it had no route information to work with.

SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON

Unexpected token '<', "<!DOCTYPE "... is not valid JSON

Your code expected JSON, but the server sent back an HTML page, usually a 404 page or your app’s own index.html.

This page isn’t working my-app.com redirected you too many times. Try deleting your cookies. ERR_TOO_MANY_REDIRECTS

ERR_TOO_MANY_REDIRECTS

The site keeps redirecting the browser in a circle, usually between HTTP and HTTPS or between two domains, so the browser stops trying.

Build with an agent that checks its work

The Layerwise agent clicks through the live preview and reads the console, so it catches errors before you do.