Lovable deployment failed: the 9 real causes, and how to fix each one

Published 8 September 2026 · 9 min read

The banner says publishing failed. It does not say why, the retry button changes nothing, and the AI has now rewritten three files that were working an hour ago. Here is the uncomfortable truth about this error: it is almost never one bug. It is one of nine well known differences between the environment where your app runs fine and the environment where it is built for real. This page names all nine, tells you how to recognise each one in thirty seconds, and gives you the fix.

First, stop clicking retry and go read the build log

The red banner reports one fact: the build process exited with an error code. That is all it knows. The actual message, the one that names a file and a line number, is in the build log, and almost nobody opens it because it looks intimidating.

Open the failed deployment and look for the link to the build output. If your project is connected to GitHub and hosted on Vercel or Netlify, the log lives in that provider's dashboard, under the failed deployment. Then read it from the bottom up and stop at the first line that begins with error, Error:, or a file path followed by two numbers, as in src/components/Dashboard.tsx:42:19.

That single line is worth more than any amount of prompting. It is also what you should send to support, instead of a description of what you were doing when it broke.

Cause 1. A TypeScript error the preview was hiding

How you recognise it: the log contains error TS followed by a number, and a path into src/.

This is the single most common cause, and it surprises everyone. The preview runs a development server that transpiles your code, stripping the types away without checking them. The production build compiles it, and type checking is on. So a mistake that has been sitting in your project for a week only surfaces the first time you try to publish.

The fix: the error names the file and the line. Nine times out of ten it is a property that does not exist on an object, or a value that might be null and is used as if it never could be. Ask the AI to fix that specific file and that specific line, quoting the error message verbatim. This is exactly the case where the automatic fix works well, because you have handed it a precise target.

Cause 2. Environment variables that exist in the preview and nowhere else

How you recognise it: the build succeeds, then the live page is blank or throws Cannot read properties of undefined in the browser console. Or the build itself fails on a missing key.

Your Supabase URL, your API keys and your Stripe keys were entered somewhere while you were building. That store is not automatically the same store the production build reads from. Every variable your app needs has to exist, with a production value, in the environment that actually runs the build.

The fix: list every variable your code reads, then check each one is declared for production. Do not copy them from memory: copy them from the source, because a single trailing space in an API key produces an authentication error that looks like a completely different problem.

Cause 3. A variable that is not prefixed, so the browser never sees it

How you recognise it: the variable is definitely declared, and it is still undefined in the browser.

Lovable projects are built with Vite, and Vite deliberately refuses to expose your server variables to the browser. Only variables whose name starts with VITE_ are injected into the client bundle. A key named SUPABASE_ANON_KEY will be silently empty in the browser forever. Named VITE_SUPABASE_ANON_KEY, it works.

The fix: rename the variable with the prefix in both places, the declaration and the code that reads it. And take thirty seconds to check what you are exposing: anything prefixed VITE_ ends up readable in your published JavaScript. An anon key belongs there. A service role key never does, and if one is currently sitting in your front end, rotate it today.

Cause 4. localhost hardcoded in a fetch call or a redirect

How you recognise it: the console on the live site shows a failed request to http://localhost:3000 or 127.0.0.1.

AI generators write absolute URLs when they need a working example, and localhost is the working example. It works in the preview, because the preview is localhost. It cannot work anywhere else, since the address refers to the visitor's own machine.

The fix: search the whole project for localhost. Replace API calls with relative paths, and anything that must be absolute with an environment variable holding your production URL. Then check the same thing in your authentication settings, which brings us to the next one.

Cause 5. The login loop, caused by two URLs nobody told you about

How you recognise it: the app deploys, you click sign in, you authenticate, and you land back on the login page. Or the magic link in your inbox points at localhost.

Supabase will only send an authenticated user back to an address you have explicitly allowed. When your project was created, that list contained your preview address. Your production domain was added to it by nobody.

The fix: in your Supabase project, open Authentication, then URL Configuration. Set the Site URL to your production address and add it to the redirect list. Add the preview address too if you still work there. This one takes two minutes and accounts for a remarkable share of the projects that arrive at our door described as "broken after deployment".

Cause 6. An import whose capitalisation only matters on Linux

How you recognise it: the log says Could not resolve "./components/button" for a file you can see with your own eyes.

Your machine and the preview very likely use a filesystem that treats Button.tsx and button.tsx as the same file. The Linux server that builds your app for production does not. One import written with the wrong initial, months ago, is invisible until the day you publish.

The fix: match the case exactly, character for character, between the import and the real filename. If several imports are wrong, rename nothing: correct the imports, because renaming files with only a change of case creates its own mess in version control.

Cause 7. Migrations that never ran on the production database

How you recognise it: the app loads, and every list is empty. The console shows relation "public.something" does not exist.

The tables you created while building exist in the database you were building against. If production points at a different project, or if the database was reconnected at some point, the schema simply is not there. Nothing warns you, because an empty database is a perfectly valid database.

The fix: if your project is synced to GitHub, look in the supabase/migrations folder. Your whole schema is there as SQL files, in order. Run them against the production project in the SQL editor and the structure comes back without recreating a single table by hand. That folder is the difference between an afternoon and a rebuild, and most people never open it.

Cause 8. Row Level Security switched on with nothing behind it

How you recognise it: the tables exist, the data is visibly in them, and your app receives empty arrays. No error, anywhere.

This is the most misleading failure of the lot, because everything is working exactly as designed. Row Level Security is on, which means access is denied by default. No policy has been written, which means nothing is ever allowed. The query succeeds and returns nothing, and since there is no error message, you go looking for a bug that does not exist.

The fix: write the policies. One per table, per operation you actually need, scoped to the authenticated user. Resist the shortcut of disabling Row Level Security to make the data appear: your database becomes readable by anyone holding a key that is published in your JavaScript. If you take one thing from this page, take this one.

Cause 9. No rewrite rule, so every link works except the ones people share

How you recognise it: the home page is fine. Navigating inside the app is fine. Refreshing the page, or opening a link someone sent you, returns a 404.

Your app has one real file, index.html, and the routes are drawn by JavaScript once it has loaded. When a visitor arrives directly at /dashboard, the host looks for a file at that path, finds nothing, and answers 404 before your JavaScript has a chance to exist.

The fix: tell the host to serve index.html for every unknown path. On Netlify that is one line in a _redirects file: /* /index.html 200. On Vercel it is a rewrite rule in vercel.json. One line, and every shared link starts working.

It says deployed, and the app is still broken

A green tick only means the build produced files. Three symptoms come up again and again after a successful deployment, and each maps to a cause above.

What you seeWhere to lookMost likely cause
Blank white pageBrowser console on the live URLMissing variable (2 or 3), or localhost (4)
Login sends you back to loginSupabase, URL ConfigurationRedirect URLs (5)
Everything loads, all lists emptyNetwork tab, response bodiesMissing migrations (7) or Row Level Security (8)
404 when refreshing a pageHost configurationNo rewrite rule (9)
Old version still showingWhich branch the host buildsDeployment pointing at the wrong branch

When to stop prompting, with numbers

The automatic fix is genuinely good at causes 1 and 6, where the log hands it a file and a line. It is close to useless on causes 2, 3, 5, 7, 8 and 9, because the problem lives in a settings panel or a database it cannot see. It will still try, confidently, and it will change your code while trying.

A reasonable rule: two attempts. If the second one produces the same error message as the first, the third will too. Each round costs credits, and more importantly each round adds edits to files that were fine, which is how a project that had one problem on Monday has four by Thursday.

Before anything else, protect what you have. Connect the project to GitHub if it is not already, and make a commit you can return to. It costs nothing and it is the difference between an experiment and a gamble.

If you have already been round this loop several times, the problem is probably no longer the deployment. Our guide on what to do with a stuck Lovable app covers the five blockers underneath, what is worth salvaging, and how to decide between prompting on, hiring by the day, or handing it over.

We finish your app and put it live for €299

Send us the link to your Lovable, Bolt or v0 project. Within 24 hours we tell you in writing what we keep, what we rebuild, and your app is live 72 hours after your go. Full source code included, and you pay nothing before approving the scope.

Get my free audit

Deployment failures: the essentials

The real error
In the build log, not in the banner. Read from the bottom up, stop at the first line naming a file and a line number
Most common cause
A TypeScript error the development preview was not checking for
Most misleading cause
Row Level Security enabled with no policy: queries succeed and return nothing, with no error
Fastest fix on the list
Adding your production domain to the Supabase redirect URLs, about two minutes
When to stop prompting
After two attempts that return the same error message
Handing it over
MonMVP, €299 including tax, written diagnosis within 24 hours, live in 72 hours, source code yours

Common questions

Why does it work in the preview and fail on publish?

Because those are two different environments and the differences are exactly where projects break. The preview does not type check, keeps its own variables, and runs on a filesystem that ignores capitalisation. The production build type checks, only sees the variables you declared for production, and runs on Linux where capitalisation is law. Nothing is wrong with your project: it has simply never been through a real build until now.

The build passes and the page is white. What now?

Open the browser console on the live address. A blank page means JavaScript threw before rendering. The message will point at one of three things: a variable that is undefined, a request to localhost, or a route that does not exist. All three are covered above, in causes 2 to 4 and cause 9.

Is it safe to just turn off Row Level Security?

No, and it is the one shortcut on this page that can genuinely hurt you. Your anon key is published inside your JavaScript, by design, and it is meant to be harmless precisely because Row Level Security stands behind it. Remove that and anyone who opens the developer tools can read and write your entire database. Write the policies instead. Two or three per table is usually the whole job.

How do I know if the project is worth saving?

Almost always yes, for the parts that matter. The screens, the layout and the user journeys are what these tools do well, and they are kept in the large majority of takeovers. Authentication, access rules and deployment are the parts that get rebuilt properly, and that is normal rather than a sign of failure. Starting from zero is rarely the right call.

Can you take it over instead?

That is what we do. Send the project link and we come back within 24 hours with a written scope: what we keep, what we rebuild, what is out of scope. If we can do it, it is €299 including tax and your app is live 72 hours after you say go, with the full source code handed over. If your project is bigger than that, the diagnosis says so and you have paid nothing. There is also a gallery of ten complete demo apps if you want to see the level of finish before talking to anyone.