Guides / Prompting and shipping
How to Fix 404 Errors After Deploying Your Website or App
Pages work locally but 404 after deploy? Fix SPA routing fallbacks, trailing slashes, case-sensitive paths, missing build files and API routes, step by step.
Mythex Team · · 7 min read
If your app works locally but pages return 404 after deploying, the most common cause is missing single-page app (SPA) routing: the server looks for a file called /dashboard that doesn't exist, because that route only exists in JavaScript. The fix is a fallback rule that serves index.html for app routes. Other causes are files missing from the build output, case-sensitive paths, trailing-slash differences, a wrong base path, and API routes that were never deployed.
Start by noting exactly which URL 404s and how you got there. The pattern points straight at the cause.
Step 1: Find the pattern
| What you see | Most likely cause | Go to |
|---|---|---|
| Home page works; clicking links works; refreshing or opening a deep link gives 404 | No SPA fallback | Step 2 |
| Every page 404s, including the home page | Wrong folder published, or the build didn't deploy | Step 3 |
| Pages load, but images, fonts or scripts 404 | Files missing from the build, or wrong paths | Step 4 |
Works with /about/, fails with /about (or the reverse) | Trailing-slash handling | Step 5 |
/About fails but /about works | Case-sensitive server | Step 4 |
Page loads, but data calls to /api/... 404 | API not deployed or not routed | Step 6 |
| Old URLs from a previous site 404 | Missing redirects | Step 7 |
| Only on a custom domain | DNS or domain setup | Step 8 |
To see the real status code, open developer tools (right-click → Inspect), go to the Network tab, reload, and look at the first request's status.
Step 2: Add the SPA fallback
This is the classic one. In a React, Vue or Svelte single-page app, the server only has a handful of real files: index.html and some JavaScript and CSS. Routes like /dashboard or /projects/42 are handled by a router in the browser.
- Clicking a link works: the router changes the URL and draws the new page without asking the server.
- Refreshing or opening a link directly fails: the browser asks the server for
/dashboard, the server has no such file, and it returns 404.
The fix is a rule that says: if the request doesn't match a real file, serve index.html with status 200, and let the router take over. How you add it depends on the host (check your host's current docs, as details change):
| Host | Typical fallback rule |
|---|---|
| Netlify | A _redirects file in the published folder with /* /index.html 200 |
| Vercel | A rewrite in vercel.json from all paths to /index.html (framework presets often handle this) |
| Cloudflare Pages | Serves SPA-style fallback by default when there's no top-level 404.html |
| Nginx | try_files $uri $uri/ /index.html; in the location / block |
| Apache | A rewrite rule in .htaccess sending non-file requests to index.html |
| GitHub Pages | No server-side rewrites; common workarounds are hash routing or a 404.html that redirects |
Make sure the rule doesn't swallow real files: requests for /assets/app.js must still return the JavaScript file. If they return index.html instead, you get a blank page with a "Failed to load module script" error. See fixing a blank white page.
Also give the router a catch-all route so that URLs that don't match any page show a friendly "Page not found" instead of nothing.
If you can't change server settings, hash routing (URLs like /#/dashboard) avoids the problem, because the part after # is never sent to the server. It's a workaround, not a first choice: the URLs are uglier and less friendly to search engines.
Frameworks that render pages on the server or generate them at build time (Next.js, Astro, and similar) usually have real files or server routes for each page, so this particular problem is less common there. Their equivalent is a page that was never generated at build time.
Step 3: Check what was actually deployed
If everything 404s, the server probably isn't serving your built app.
- The right folder. The host must publish the build output (commonly
distfor Vite,buildfor Create React App,outfor a static Next.js export), not the project root. - The build ran. Read the deploy log. If the build failed, the host may have deployed nothing, or an old version.
index.htmlis at the top level of the published folder, not in a sub-folder.- The base path matches. If the app is served from
example.com/app/, it must be built with that base (Vite'sbase, React Router'sbasename), or every route and asset misses.
Step 4: Fix missing files and wrong paths
When pages load but assets 404:
- Case sensitivity. macOS and Windows usually treat
Logo.pngandlogo.pngas the same file. Most Linux servers don't. Make every reference match the real file name exactly. - Files outside the build. In Vite, files in
public/are copied as-is and referenced from the root (/logo.png). Files imported in code are bundled. A file sitting elsewhere in the project may not be deployed at all. - Relative paths.
images/photo.jpgis resolved relative to the current URL, so it breaks on/blog/post-1. Use/images/photo.jpg. - Uploaded files saved to local disk. If users' uploads were written to the server's own folder, they vanish when a new version deploys. Use proper file storage.
Step 5: Handle trailing slashes consistently
/about and /about/ are different URLs. Hosts differ in whether they add or remove the slash, and a mismatch with your links or your router can produce 404s or redirect loops. Pick one style, make your links use it, and configure the host to redirect the other form to it. Consistency also helps search engines, which otherwise may see two copies of each page.
Step 6: Fix API routes that 404
If the page loads but the Network tab shows /api/... requests returning 404:
- The backend isn't deployed. A static host serves files only; a backend must run somewhere.
- The path is routed differently in production. In development, a proxy may have forwarded
/apito a separate server. In production, the host needs an equivalent rewrite, or the frontend must call the API's real address. - The SPA fallback caught it. An API request that returns
index.htmlwith status 200 means the fallback rule is too broad. Exclude/api/*from it.
The published app's requests to /api/orders return 404, but they work in the preview. Check how /api is routed in the preview vs the published app, and make the published frontend reach the backend the same way.
Step 7: Redirect old URLs
If you've moved from another site or restructured, old URLs that people and search engines still use will 404. Add a 301 (permanent) redirect from each old URL to its new home. Keep a list and test a sample after launch. Our guide on migrating a WordPress site covers building that list.
Step 8: Check the domain
If the app works at the host's address but 404s on your custom domain, the domain may be pointing at the wrong project, or the host may not have the domain attached to your site yet. Check the DNS records and the host's domain settings. See how to connect a custom domain.
Make your real 404 page useful
Some 404s are correct: someone mistyped a URL. Make that page helpful:
- A short message and a link to the home page and key sections
- A search box, if the site has search
- The same header and navigation as the rest of the site
Where your setup allows, return a real 404 status for pages that don't exist. A "not found" message returned with status 200 is what search engines call a soft 404, and it can waste their crawl time and confuse indexing.
Common mistakes
- Testing only by clicking. Always test by pasting deep URLs into a new tab and refreshing.
- A fallback that catches everything. Assets and API routes return
index.html, which causes blank pages and confusing API errors. - Mismatched capitalisation that only breaks on the server.
- Deploying the source folder instead of the build output.
- Restructuring URLs without redirects. Every old link becomes a 404.
404s in Mythex
In Mythex you publish from the workspace, and static front ends deploy as static sites. If a published page 404s, give the agent the exact URL, whether it happens on refresh or on click, and what the Network tab shows, and ask it to check the routing. Publish again after the fix, since the live app is a snapshot until you republish. If you recently changed the project and the live site looks old, publish again and hard-refresh. Use Fix with AI in the publish dialog if the build fails. See How to publish and Publish troubleshooting in the docs, and our guide to debugging an AI-built app.
404 checklist
- Noted exactly which URLs 404 and whether on click, refresh or both
- SPA fallback serves
index.htmlfor app routes, with status 200 - Fallback excludes real files and
/api/* - Router has a catch-all "not found" route
- Build output folder is what's published, and the build succeeded
- Base path matches where the app is served
- File name capitalisation matches every reference
- Asset paths start with
/ - Trailing-slash style consistent
- API routes deployed and routed in production
- 301 redirects for old URLs
- Deep links tested by pasting them into a new tab
Questions
Why do I get a 404 when I refresh a page in my React app?
In a single-page app, routes like /dashboard exist only in JavaScript. Clicking a link works because the router handles it in the browser, but a refresh asks the server for a /dashboard file that doesn't exist. The fix is a fallback rule that serves index.html for app routes.
How do I add an SPA fallback on Netlify, Vercel or Nginx?
On Netlify, add a _redirects file with /* /index.html 200. On Vercel, add a rewrite in vercel.json that sends all paths to /index.html. On Nginx, use try_files $uri $uri/ /index.html; in the location block. Check your host's docs, as rules change.
Why do my images or files 404 after deploying?
Usually the file isn't in the build output, the path's capitalisation doesn't match (servers are often case-sensitive where your laptop isn't), or the path is relative when it should start with a slash.
Should a missing page return a real 404 status?
Yes. Serve a helpful not-found page, but with a 404 status where your setup allows it. Search engines treat a 'not found' message returned with status 200 as a soft 404, which wastes crawl time and can confuse indexing.