Hire Lovable Xperts
Deployment

Lovable App Returns 404 on Refresh or Deep Link? The SPA Routing Fix

If your Lovable app loads from the homepage but throws a 404 NOT_FOUND the moment you refresh a sub-page or open a deep link, your host is looking for a real file at that path and not finding one. Your React router only exists in the browser. The fix is a one-line rewrite rule that tells Vercel or Netlify to serve index.html for every route and let the client-side router take over. Copy-paste configs are below.

By Founder Name · Last verified: 2026-06-25

Why does my Lovable app return a 404 when I refresh the page?

Because the route only exists inside your JavaScript bundle, not on the server. Lovable apps are single-page apps (SPAs): the entire router lives in the browser. When you visit /dashboard directly or hit refresh there, Vercel or Netlify looks for a literal file at /dashboard, finds nothing, and returns a 404. The homepage works because index.html is the one file that physically exists.

Here is the exact sequence. On the first visit your browser downloads index.html and the JavaScript bundle. React Router then intercepts every link click and swaps the view without ever asking the server again — that is why navigating inside the app works perfectly. The problem only appears when the browser makes a fresh request to the server: a manual refresh, a pasted URL, a bookmark, or a back-button into a deep link. The server has no idea /dashboard is a client route; to it, that is just a missing file.

This is one of the most misdiagnosed Lovable deploy failures. Builders assume the page is broken, re-prompt the app to rebuild routing, and burn credits chasing a bug that was never in their code. The route works. The host configuration is the missing piece — and it is a five-minute fix you make on the host, not in Lovable.

Lovable SPA 404 Diagnostic — Symptom vs Cause
What you seeWhen it happensReal cause
Homepage works, refresh on /dashboard 404sManual refresh on any sub-pageNo SPA fallback rewrite on the host
404 NOT_FOUND with Vercel ID footerRefresh or deep link on VercelMissing rewrite in vercel.json
Plain 'Page not found' on NetlifyRefresh or deep link on NetlifyMissing _redirects or netlify.toml rule
Links work, pasted URLs 404Sharing a deep link to a teammateServer has no fallback to index.html
Works in Lovable preview, 404s in productionOnly on the deployed custom domainLovable preview rewrites; your host does not
404s only after a hash-router migrationAfter switching to BrowserRouterHash routes never hit the server; clean URLs do

How do I fix the 404 on refresh on Vercel?

Add a vercel.json file to your project root with a single catch-all rewrite that points every path at index.html. This tells Vercel to serve your SPA shell for any URL it does not recognise as a real file, so React Router can resolve the route in the browser. Commit the file, push, and redeploy. The 404 disappears on the next deployment.

Create vercel.json in the repository root (the same folder as package.json) with exactly this:

{ "rewrites": [ { "source": "/(.*)", "destination": "/index.html" } ] }

A rewrite is not a redirect. The browser URL stays /dashboard — Vercel simply serves the contents of index.html under the hood, and your router reads the path and renders the right view. Do not use a redirect here: a 301 or 302 would change the URL to / and break deep linking entirely.

One caveat for projects using Vite's default output: Vercel auto-detects most SPA frameworks and applies this rewrite for you, but a custom build output directory, a non-standard framework preset, or an older project created before auto-detection can leave the fallback missing. Adding vercel.json explicitly is the deterministic fix — it works regardless of what Vercel detects.

  1. Create a file named vercel.json in your project root (next to package.json).
  2. Paste the rewrites block above into it exactly, including the outer braces.
  3. Commit and push to the branch Vercel deploys from (usually main).
  4. In the Vercel dashboard, confirm the new deployment finished — or trigger a redeploy.
  5. Open a deep link directly (yourapp.com/dashboard) and hard-refresh to confirm the 404 is gone.
Do not set the rewrite destination to a route like /home or use a 301 redirect. That changes the URL in the address bar and silently breaks every deep link and shared URL. The destination must be /index.html and it must be a rewrite, not a redirect.

Related: full Lovable to Vercel migration guide · other Lovable deployment errors

How do I fix the 404 on refresh on Netlify?

Netlify needs a SPA fallback rule too, and there are two ways to add it. The simplest is a _redirects file in your build output (the public or dist folder) with a single catch-all line. The status code 200 is what makes it a rewrite instead of a redirect, so the URL stays intact and your client router resolves the deep link.

Option A — the _redirects file. Create a file named exactly _redirects (no extension) in the folder Netlify publishes from, with this one line:

/* /index.html 200

Option B — netlify.toml in the project root, which survives across builds and is easier to keep in version control:

[[redirects]] from = "/*" to = "/index.html" status = 200

The trailing 200 is the entire trick. Without it, Netlify issues a 301 redirect, the address bar collapses to /, and deep linking breaks. With 200, Netlify serves index.html in place while preserving the original URL — exactly what an SPA needs. If you use both files, netlify.toml wins, so pick one to avoid confusion.

Vite users: make sure _redirects ends up in dist, not just the project root. Place it in the public folder so Vite copies it into the build output automatically — files in public are emitted verbatim, while a _redirects in the repo root is ignored by the build.

  1. Add the _redirects line (or the netlify.toml block) to your project.
  2. For Vite, put _redirects inside the public folder so it lands in dist on build.
  3. Commit, push, and let Netlify rebuild — or drag-and-drop a fresh build in the Netlify UI.
  4. Open a deep link and refresh to confirm the catch-all rewrite is serving index.html.
If you migrated off Lovable to Netlify and still 404, confirm the _redirects file is actually in your published directory. Open the deploy's file browser in Netlify and look for it under the publish folder — a _redirects sitting in the repo root but not in dist does nothing.

Related: moving your Lovable app to Netlify

What exactly is a rewrite and why does it stop the 404?

A rewrite is a server instruction that says: when a request comes in for a path I do not recognise, serve this file instead without changing the URL. For an SPA, that fallback file is index.html. The browser keeps the original path in the address bar, your bundled router reads it, and renders the matching view — so the server never needs to know your routes exist.

Contrast this with a redirect. A redirect (301 or 302) tells the browser to go fetch a different URL, and the address bar changes to match. That is wrong for an SPA: if /dashboard redirects to /, the user lands on the homepage and the deep link is destroyed. A rewrite preserves the path while quietly serving the shell — the URL stays /dashboard, React Router sees /dashboard, and the dashboard renders.

This is why your Lovable preview never 404s but your production host does. The Lovable editor's hosting applies a SPA fallback automatically. The moment you connect a custom domain or deploy to your own Vercel or Netlify account, you inherit that host's default behaviour — which, without the config above, is to treat every path as a real file and 404 when the file is not there.

Why does it work locally but 404 only in production?

Your local dev server (Vite's npm run dev) has a built-in SPA fallback — it serves index.html for unknown routes automatically, so refresh always works. Production hosts do not assume that. When you build and deploy, you are running on a static file server that returns exactly what is on disk, and your routes are not on disk. The mismatch is the dev server being forgiving and production being literal.

This gap traps a lot of builders mid-migration. The app behaves perfectly through every local test and every click inside the deployed app, so the 404 looks random. It is not random — it only ever fires on a fresh server request to a client-only route, which your click-through testing rarely triggers. Always test deep links by pasting a sub-page URL into a new tab and hard-refreshing, not by navigating from the homepage.

If you serve a production build locally with vite preview, you can reproduce the exact 404 before you ever deploy — vite preview is stricter than the dev server and will expose a missing fallback. That is the fastest way to confirm whether your rewrite config is correct without burning a deploy cycle.

Quick reproduction: run your production build, serve it with vite preview, then paste a deep link like localhost:4173/dashboard into a new tab. If it 404s locally, it will 404 in production — and the rewrite config above is your fix.

I added the config and it still 404s — what now?

Most lingering 404s after adding a rewrite come from the config not reaching the server: the file is in the wrong directory, the change was not redeployed, or a stale build is still live. Work the list below in order. Each item rules out a specific failure, and together they catch the overwhelming majority of stubborn SPA 404s on both Vercel and Netlify.

A subtle one worth calling out is The Stale-Commit Deploy — your fix is committed locally but the host is still serving an older build because the push never triggered a deployment, or auto-deploy is pointed at a different branch. Always confirm in the host dashboard that the latest deployment timestamp matches your most recent commit before assuming the config is wrong. The config is usually right; the deploy is stale.

If you have walked the entire checklist and the deep link still 404s, the cause is structural — a custom build output path the host cannot find, a base-path mismatch in vite.config, or a router configured with the wrong basename. That is the point to stop guessing and have an engineer read the actual build output and host settings rather than re-prompting Lovable to rebuild routing.

  1. Confirm the config file is in the right place: vercel.json in the repo root; _redirects inside the published build folder (public for Vite).
  2. Verify the file is a rewrite (Vercel rewrites / Netlify status 200), not a redirect.
  3. Check the host dashboard: does the latest deployment timestamp match your most recent commit? If not, you have a Stale-Commit Deploy — redeploy.
  4. Hard-refresh with the cache disabled (DevTools open, Network tab, Disable cache) to rule out a cached 404.
  5. Confirm your router uses BrowserRouter with the correct basename and that the build output directory matches what the host serves.
  6. If it still 404s, reproduce locally with vite preview to isolate config from deploy issues.

Related: the deployment errors hub

Should I switch to a hash router to avoid this entirely?

You can, but it is the wrong trade. A hash router (URLs like yourapp.com/#/dashboard) never sends the route to the server, so it sidesteps the 404 without any host config. The cost is ugly URLs, weaker SEO, and broken deep-link previews on social and search. For any app you want indexed or shared, keep clean BrowserRouter URLs and add the rewrite — it is the better long-term fix.

Hash routing is a legitimate escape hatch when you genuinely cannot configure the host — some locked-down static buckets, certain corporate file servers. But Vercel and Netlify both support SPA rewrites in one file, so on those platforms there is no reason to accept the downsides. If your Lovable app already shipped with clean URLs and is now 404ing, the rewrite preserves your existing links; switching to hash routing would change every URL you have already shared.

If you are weighing this decision as part of a larger move off Lovable, it usually rides along with custom-domain setup, environment variables, and build configuration — all of which fail in similar quiet ways. Getting the whole deploy pipeline configured once, correctly, is cheaper than fixing each symptom as it surfaces.

When should I bring in an engineer instead of fixing this myself?

If you have added the correct rewrite, confirmed a fresh deploy, and the deep link still 404s, the problem is no longer the SPA fallback — it is a build-path, base-path, or router-config mismatch that needs someone reading your actual project. That is also true if the 404 is tangled up with custom-domain, environment-variable, or build failures all hitting at once during a migration.

The honest cost calculus: the single rewrite line above is a five-minute self-fix and you should absolutely try it first. But if it does not resolve, do not loop on re-prompting Lovable to regenerate routing — that burns credits on code that was never the problem. A senior engineer configures the host correctly once, verifies every deep link, and hands back a deploy pipeline that does not 404. If your app is live and 404ing for real users right now, that is an emergency worth escalating fast.

Related: Lovable App Rescue service · book an emergency deploy fix

Frequently asked questions

Why does my Lovable app 404 on refresh but work when I click links?
Clicking links is handled entirely by your in-browser router, which never asks the server again. A refresh or a pasted URL makes a fresh request to the host for that exact path — and the host has no file there, so it 404s. The route only exists in your JavaScript. Adding a SPA rewrite that serves index.html for every path fixes it on the host side.
What is the exact vercel.json to stop the 404 on refresh?
Create vercel.json in your project root containing a rewrites array with one rule: source "/(.*)" and destination "/index.html". That single catch-all rewrite serves your SPA shell for any unrecognised path so React Router can resolve it in the browser. Commit, push to your deploy branch, and let Vercel redeploy. The 404 is gone on the next deployment.
What is the Netlify _redirects line for a SPA 404 fix?
Add a file named _redirects to your published build folder with the single line: /* /index.html 200. The 200 status is what makes it a rewrite that preserves the URL rather than a redirect that collapses it to the homepage. For Vite, put _redirects in the public folder so it gets copied into dist. A netlify.toml redirects block with status 200 works identically.
Why does the 404 only happen in production and not in local dev?
Vite's dev server has a built-in SPA fallback that serves index.html for unknown routes, so refresh always works locally. Production static hosts return exactly what is on disk and your routes are not files on disk, so they 404. Reproduce it before deploying by serving your production build with vite preview and pasting a deep link into a new tab.
Is a rewrite the same as a redirect for fixing this?
No, and the difference matters. A rewrite serves index.html while keeping the original URL in the address bar, so your router resolves the deep link. A redirect changes the URL — usually to the homepage — which destroys deep linking. Always use a rewrite (Vercel rewrites array, or Netlify status 200). A 301 or 302 here will break exactly what you are trying to fix.
I added vercel.json and it still 404s — what did I miss?
Three usual culprits: the file is not in the repository root next to package.json; the change was never redeployed (a Stale-Commit Deploy where the host is still serving an old build); or a cached 404 is masking the fix. Confirm the latest deployment timestamp in the Vercel dashboard matches your most recent commit, then hard-refresh with the cache disabled.
Should I use a hash router to avoid configuring the host?
Only as a last resort. A hash router (URLs with #/) never sends the route to the server so it dodges the 404 without config, but it gives you ugly URLs, weaker SEO, and broken link previews. On Vercel and Netlify a one-line rewrite is the better fix, and it preserves all the clean URLs you have already shared. Keep BrowserRouter and add the rewrite.
Does this 404 mean my Lovable app or my code is broken?
Almost never. The route works fine — the issue is purely host configuration: your server lacks the SPA fallback that serves index.html for client-only routes. Re-prompting Lovable to rebuild routing will not fix it and will burn credits on code that was never the problem. The fix lives in vercel.json or _redirects on the host, not in your app.
Why does my custom domain 404 when the Lovable preview did not?
The Lovable editor's hosting applies a SPA fallback automatically, so deep links always resolve in the preview. When you point a custom domain at your own Vercel or Netlify account, you inherit that host's default static-file behaviour, which 404s on unknown paths. Add the rewrite config for your host and the custom domain will match the preview's behaviour.
My deploy still 404s after the correct config — can someone just fix it?
Yes. If the rewrite is correct and freshly deployed but deep links still 404, the cause is usually a build-output path, base-path, or router basename mismatch that needs an engineer reading your project. We configure the host, verify every deep link, and return a deploy pipeline that does not 404. If real users are hitting 404s right now, book an emergency fix and we triage the same day.

App down or leaking data? Get an expert on it within 24–48h.

Book a free 30-minute audit call. We'll diagnose what's wrong and tell you exactly what it costs to fix.

Get emergency help