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.
| What you see | When it happens | Real cause |
|---|---|---|
| Homepage works, refresh on /dashboard 404s | Manual refresh on any sub-page | No SPA fallback rewrite on the host |
| 404 NOT_FOUND with Vercel ID footer | Refresh or deep link on Vercel | Missing rewrite in vercel.json |
| Plain 'Page not found' on Netlify | Refresh or deep link on Netlify | Missing _redirects or netlify.toml rule |
| Links work, pasted URLs 404 | Sharing a deep link to a teammate | Server has no fallback to index.html |
| Works in Lovable preview, 404s in production | Only on the deployed custom domain | Lovable preview rewrites; your host does not |
| 404s only after a hash-router migration | After switching to BrowserRouter | Hash 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.
- Create a file named vercel.json in your project root (next to package.json).
- Paste the rewrites block above into it exactly, including the outer braces.
- Commit and push to the branch Vercel deploys from (usually main).
- In the Vercel dashboard, confirm the new deployment finished — or trigger a redeploy.
- Open a deep link directly (yourapp.com/dashboard) and hard-refresh to confirm the 404 is gone.
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.
- Add the _redirects line (or the netlify.toml block) to your project.
- For Vite, put _redirects inside the public folder so it lands in dist on build.
- Commit, push, and let Netlify rebuild — or drag-and-drop a fresh build in the Netlify UI.
- Open a deep link and refresh to confirm the catch-all rewrite is serving index.html.
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.
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.
- Confirm the config file is in the right place: vercel.json in the repo root; _redirects inside the published build folder (public for Vite).
- Verify the file is a rewrite (Vercel rewrites / Netlify status 200), not a redirect.
- Check the host dashboard: does the latest deployment timestamp match your most recent commit? If not, you have a Stale-Commit Deploy — redeploy.
- Hard-refresh with the cache disabled (DevTools open, Network tab, Disable cache) to rule out a cached 404.
- Confirm your router uses BrowserRouter with the correct basename and that the build output directory matches what the host serves.
- 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?
What is the exact vercel.json to stop the 404 on refresh?
What is the Netlify _redirects line for a SPA 404 fix?
Why does the 404 only happen in production and not in local dev?
Is a rewrite the same as a redirect for fixing this?
I added vercel.json and it still 404s — what did I miss?
Should I use a hash router to avoid configuring the host?
Does this 404 mean my Lovable app or my code is broken?
Why does my custom domain 404 when the Lovable preview did not?
My deploy still 404s after the correct config — can someone just fix it?
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.