Free 15-minute launch audit — we tell you exactly what stands between you and going live.Claim a slot
Launch

Lovable to production: what to fix before you launch

Lovable gets you a working app in an afternoon. Here is what still stands between that and a launch you can charge money for, in the order it matters.

GEGen2ProdProduction engineering

6 min read

A Lovable preview window beside a production deployment pipeline

Lovable is genuinely good at what it does. Describe an app, watch it appear, click through a working product before lunch. The same is true of Bolt, v0, and Cursor — the generation step is solved.

What none of them solve is the second half: the part where strangers use the thing, some of them are hostile, and money changes hands. That gap is not a criticism of the tool. It is a different problem, and the tool was never asked to work on it.

Here is what we find on the other side of that gap, in the order we fix it.

What you actually have when Lovable says "done"

LayerWhat you getWhat is missing
UIComplete, responsive, usually attractiveLoading and error states for real latency
AuthSign-up, sign-in, session handlingAny rule about who may read which rows
DatabaseTables that match your descriptionIndexes, constraints, and RLS policies
IntegrationsWorking API callsKeys moved out of the browser
HostingA preview URLA domain, a pipeline, and a rollback
OperationsEverything

That last row is the one that surprises people. A prototype has no operational surface at all, and nothing in the generation step will tell you that, because from inside the preview everything is fine.

1. The Supabase defaults are open until you close them

Lovable wires Supabase in for you, and it does it well enough that the app works — which is exactly the problem. A table with Row Level Security disabled is readable and writable by the anon key that ships in your JavaScript bundle. Not "in theory". With a browser console and thirty seconds.

Run this against your project before anything else:

select tablename, rowsecurity as rls_enabled
from pg_tables
where schemaname = 'public'
order by rls_enabled, tablename;

Every false is a table the internet can read and write. We have written the five specific policy mistakes up in detail — the Supabase RLS mistakes we find in almost every AI-generated app — because it is the single highest-severity category of finding we have, every quarter, without exception.

2. Your secrets are in the browser

Anything prefixed NEXT_PUBLIC_, VITE_, or PUBLIC_ is compiled into the bundle and served to every visitor. AI tools reach for that prefix when a variable is not visible at build time, because it makes the error go away.

Sometimes that is fine — a Supabase anon key or a Stripe publishable key is designed to be public. Sometimes it is an OpenAI key attached to your credit card. The tool cannot tell the difference, and neither can a code reviewer who is skimming. How to find the keys your AI tool leaked is the five-minute version of this audit.

3. Publishing is not deploying

The publish button gives you a URL. It does not give you a custom domain with a correct certificate, environment separation between what you are testing and what customers see, a build that fails loudly instead of shipping a broken bundle, or a way back to the previous version when a release goes wrong.

The minimum viable pipeline is smaller than it sounds:

  • A main branch that deploys automatically on merge.
  • A preview environment on every pull request, with its own database.
  • Typecheck, lint, and whatever tests exist, running before the deploy, not after.
  • One documented command — or one button — that puts yesterday's build back.

If a rollback means "re-prompt and hope", you do not have a rollback.

4. Nothing is watching

Ask yourself how you would find out that checkout has been failing for two hours. If the honest answer is "a customer emails me", that is the gap.

You need three things, and all three have usable free tiers:

NeedWhat it answers
Error trackingSomething threw. Where, for whom, how often?
Uptime checks on real user pathsNot /, but sign-in and checkout
Structured logs with a request IDWhat happened before it broke?

Set the alert to reach somewhere you actually look. An alert routed to an inbox you check weekly is a way of documenting outages after the fact.

5. The database has no indexes and no constraints

Generated schemas are shaped to be described, not to be queried. On a hundred seeded rows everything is instant, which is why nothing looks wrong until real data arrives.

Two passes fix most of it. Add an index on every column used in a where, an order by, or a join. Then add the constraints that keep bad data out in the first place: not null where a null is nonsense, unique on anything that identifies something, a foreign key with an explicit on delete rule, and a check on every status column so a typo cannot invent a new state.

Money is the special case. Store it in integer minor units — cents, paise — never a float. 0.1 + 0.2 is not 0.3, and a rounding error in a ledger is a support ticket you cannot answer.

6. Edge functions that trust their caller

If your app has serverless functions, read each one and answer a single question: what stops an anonymous caller from invoking this?

The generated version usually reads a user_id out of the request body and gets on with it. That is not authentication — it is a field the caller typed. Verify the JWT, derive the user from it, and ignore whatever the body claims about identity.

7. No separation between testing and production

One project, one database, one set of keys. It works right up to the evening you run a migration against what turns out to be the live table.

Two Supabase projects and two sets of environment variables is an afternoon of work and it removes an entire category of catastrophe. Do it before you have customers, because afterwards it involves a migration.

8. The file nobody wants to open

Somewhere there is a component of 800 to 1,500 lines holding state, data fetching, business rules, and markup at once. It is not a style problem. It is the reason every change breaks something unrelated, and the reason your next feature takes three days instead of three hours.

You do not need to rewrite it. Pull the data fetching out, then the business rules, then split what is left along the seams that become obvious once the first two are gone. Each step is safe on its own.

9. Nobody has tested the path where money moves

The demo covers the happy path. Real payments are almost entirely unhappy paths: the card is declined, the webhook arrives twice, the customer closes the tab during redirect, the subscription lapses at 3am.

Every one of those has a correct behaviour, and none of them are exercised by clicking through the flow once. This is the one area where we insist on tests, because it is the one where a bug costs money in both directions.

The order we do it in

Two to five days, in this sequence, because each step makes the next one safe:

DayWork
1RLS and policies, secrets out of the client, function auth
1–2Constraints, indexes, environment separation
2–3Pipeline, preview environments, rollback
3–4Error tracking, uptime, alerting, logs
4–5Refactor the worst file, tests on the money path

Security first, always. A fast, beautifully monitored app with an open database is worse than a slow one with a closed one, because it fails silently and you find out from someone else.

Where to start today

Pick one thing: run the pg_tables query above. It takes a minute and it tells you whether you have a launch to prepare or an emergency to handle.

Then, if you want the rest of this run properly against your codebase — prioritised, written up, and fixed — that is exactly what a production sprint is. It starts with a free 15-minute launch audit: we look at your repo and tell you honestly what stands between you and going live. No pitch, no obligation.

Share

Your AI-built product deserves a real launch.

Start with a free 15-minute call. We'll tell you honestly what it takes to get you live — no pressure, no jargon.

Senior engineers only · NDA on request · Your code stays yours