Why is my Lovable app broken?
appears when:After the Lovable preview is published to a production domain and real users start hitting the app
Why is my Lovable app broken? The 5 most common failures (2026)
The Lovable preview hides your Supabase URL, your permissive policies, and your OAuth callbacks. Production hides none of that. Five configuration seams account for almost every panic call we take.
localhost or the Lovable preview domain, (4) the Stripe webhook is registered against the preview URL with a test-mode secret, and (5) Supabase Edge Functions reject real browsers because CORS is wide open or missing entirely. Identify which seam you’re on using the console error string, then jump to the matching step below.Quick fix for broken Lovable app
60-second triage: open your production site in an incognito window, open DevTools, and reload. Three windows tell you almost everything.
- Console — if the first error is
supabaseUrl is required, you have missing env vars. Skip to Step 1. - Network tab — filter for
fetch/XHR. A401or403on a PostgREST call (URL contains/rest/v1/) points at RLS. ACORSbadge on a/functions/v1/call is Edge Function CORS. - Application → Cookies — no
sb-access-tokenafter login means OAuth redirect is broken. Check for alocalhostin the URL bar after sign-in.
If none of those match, the remaining failure is almost always Stripe — checkout succeeded but the webhook never activated the plan. Jump to Step 4 and check Stripe Dashboard → Developers → Webhooks → recent deliveries.
Deeper fixes when the quick fix fails
01 · Env vars — the actual failure mode, with the log line
The error you will see in the production console on first load is almost always this exact string:
01# Browser console after production deploy02Uncaught Error: supabaseUrl is required.03 at new SupabaseClient (supabase-js.mjs:??)04 at createClient (supabase.ts:3)05 at <anonymous> (layout.tsx:12)06 07# Or on Vite builds:08[vite:define] Could not resolve import.meta.env.VITE_SUPABASE_URL09error during build:10Error: Missing required env var VITE_SUPABASE_URL02 · Env vars — the copy-paste fix
List every key Lovable set, then mirror them to the host. Keep build-time variables (NEXT_PUBLIC_*, VITE_*) on a separate line — they bake into the bundle and need a fresh deploy to take effect. Deeper dive at env variables not loading on Vercel.
01# Required by every Lovable + Supabase app02NEXT_PUBLIC_SUPABASE_URL=https://<project-ref>.supabase.co03NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOi...04SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOi... # server-only, NEVER NEXT_PUBLIC_05 06# If the app uses Stripe07STRIPE_SECRET_KEY=sk_live_... # live, not sk_test_08STRIPE_WEBHOOK_SECRET=whsec_... # the live-mode endpoint's secret09 10# If OAuth is enabled11NEXT_PUBLIC_SITE_URL=https://yourapp.com # must be your real domain03 · RLS — the log line that tells you the database is actually deny-all
Supabase Dashboard → Logs → Postgres Logs. Filter ERROR. You are looking for one of these two lines after a failed INSERT or SELECT:
01-- The explicit deny02ERROR: new row violates row-level security policy for table "tasks"03 STATE: 4250104 05-- The silent deny (read side)06-- Client sees: HTTP 200 OK with []07-- Postgres log shows the query ran but no rows matched any SELECT policy08 09-- The worst one: RLS disabled entirely10WARNING: RLS is disabled on table "tasks"11 Rows are visible to every role including anon.04 · RLS — the minimum correct policy
The copy-paste fix for single-tenant ownership. Every table with user data needs all four policies (SELECT, INSERT, UPDATE, DELETE) scoped to authenticated, not public — public includes anon. For the tool-agnostic deep dive see Supabase RLS blocking insert.
01-- Enable RLS02alter table public.tasks enable row level security;03 04-- SELECT: see your own rows05create policy "tasks_select_own" on public.tasks06 for select to authenticated07 using (auth.uid() = user_id);08 09-- INSERT: write rows owned by you10create policy "tasks_insert_own" on public.tasks11 for insert to authenticated12 with check (auth.uid() = user_id);13 14-- UPDATE: modify your own rows15create policy "tasks_update_own" on public.tasks16 for update to authenticated17 using (auth.uid() = user_id)18 with check (auth.uid() = user_id);19 20-- DELETE: remove your own rows21create policy "tasks_delete_own" on public.tasks22 for delete to authenticated23 using (auth.uid() = user_id);24 25-- Default the ownership column so clients cannot forget it26alter table public.tasks27 alter column user_id set default auth.uid();05 · OAuth — the error string in the URL bar
If you’re bouncing back to the login page after a successful Google sign-in, inspect the URL bar during the redirect. You will see one of these. The tool-agnostic fix is OAuth callback URL not working in production.
01# Google's own error (most common)02https://accounts.google.com/signin/oauth/error/v2?03 authError=...&error=redirect_uri_mismatch04 05# Supabase rejects the callback06https://yourapp.com/?error=invalid_request07 &error_description=Redirect+URL+is+not+allowed08 09# Session disappears after redirect10# URL is correct but application cookies are empty:11# Missing: sb-access-token, sb-refresh-token12# Cause: Site URL still points at the preview domain06 · OAuth — fix Supabase Site URL and Google OAuth client
Both sides must agree on the production domain. The order matters: fix Supabase first so the token exchange lands, then fix Google so the consent step accepts the redirect.
- Supabase Dashboard → Authentication → URL Configuration. Set Site URL to
https://yourapp.com. Addhttps://yourapp.com/**to Additional Redirect URLs. - Google Cloud Console → APIs & Services → Credentials → OAuth 2.0 Client. Add your Supabase callback to Authorized Redirect URIs:
https://<project-ref>.supabase.co/auth/v1/callback. Also addhttps://yourapp.comto Authorized JavaScript Origins. - Test in a fresh incognito window. Sign in with Google. Confirm the session cookie
sb-access-tokenis set on your domain, not on*.lovable.app.
07 · Stripe — the dashboard tells you which of three it is
Stripe Dashboard → Developers → Webhooks → your endpoint → Recent deliveries. Each failed attempt shows the exact response your server sent.
- 404 Not Found— wrong URL. The endpoint registered in Stripe does not exist on your production host. Fix: register the correct URL or move the route file to the expected path.
- 400 No signatures found matching the expected signature— raw-body parsing. Your framework parsed JSON before
constructEvent()could verify. Fix:await req.text(), notawait req.json(). Deep dive: Stripe webhook not firing. - No deliveries at all— test-mode / live-mode mismatch. The webhook exists in test mode only; live events never reach it. Fix: toggle the dashboard to Live and add a second endpoint with a
whsec_live_secret. Tool-specific variant: Stripe stuck in test mode in production.
Lovable-specific gotcha: Lovable’s Supabase Edge Function template runs on the Deno runtime, which serves application/json request bodies differently from Node. The same route handler that works locally under next dev can fail signature verification under a deployed Edge Function because the body arrives pre-decoded. Move the webhook to a Node route if you see signature failures only in production.
08 · CORS — the Edge Function has to handle OPTIONS itself
Supabase Edge Functions don’t inject CORS headers automatically. If you call a function from the browser and see blocked by CORS policy, the function either didn’t handle the OPTIONS preflight or is responding with the wrong origin. Add a shared helper and import it into every function. Broader pattern at CORS error in production only.
01// supabase/functions/_shared/cors.ts02export const corsHeaders = {03 "Access-Control-Allow-Origin": "https://yourapp.com",04 "Access-Control-Allow-Headers":05 "authorization, x-client-info, apikey, content-type",06 "Access-Control-Allow-Methods": "POST, GET, OPTIONS",07 // NEVER use "*" if you send credentials (cookies, Authorization)08};09 10// supabase/functions/my-fn/index.ts11import { corsHeaders } from "../_shared/cors.ts";12 13Deno.serve(async (req) => {14 if (req.method === "OPTIONS") {15 return new Response("ok", { headers: corsHeaders });16 }17 18 // ... your handler ...19 return new Response(JSON.stringify({ ok: true }), {20 headers: { ...corsHeaders, "Content-Type": "application/json" },21 });22});09 · DIY vs. rescue — a practical decision matrix
Which path is right depends on how long you’ve been stuck and how much revenue is at risk. Rough guide:
| Path | Pros | Cons | Best when |
|---|---|---|---|
| DIY the 5-step fix | Free. You learn your own stack. Full control. | Easy to miss a second seam. High risk of regressions on auth and RLS. | Pre-launch. No paying users. You have a full evening. |
| Free diagnostic | Second opinion in 30 minutes. No commitment. Written findings. | Async turnaround, not same-day. Not a fix. | Stuck 2+ hours and unsure whether the bug is env, RLS, or Stripe. |
| Emergency Triage · $299 | 48-hour fixed price. Root-cause report. Single bug gone. | Scoped to one issue; multi-issue rescues need a larger package. | Paying users affected right now, one clear symptom. |
| Deploy-to-Production Pass · $1,999 | All five seams closed at once. CI, monitoring, rollback plan included. | 7-day window. Requires repo and host access. | Pre-launch and you want to do it once, correctly. |
Also see the outcome-framed engagement pages: Fix my AI app, Add payments to AI app, and Fix broken auth. For the tool-agnostic deep dives on individual errors, see the /fix/ pages linked from each step above.
Why AI-built apps hit broken Lovable app
The causal chain almost always runs the same way. Lovable generates a working preview because it has full control of the sandbox — it provisions a Supabase project, writes the tables, injects a service-role key into every server call, and bakes preview-domain URLs into OAuth and Stripe config. Every database query runs as service_role, which bypasses Row-Level Security entirely, so the model never observes the feedback signal that policies are missing. Every OAuth callback lands at a Lovable-owned URL, so the model never writes the production redirect whitelist. Every Stripe webhook is the one Lovable registered during onboarding in test mode.
At deploy the founder points a custom domain at Vercel or Netlify and copies the Supabase credentials from a Lovable panel into the host’s environment variables. Usually some get missed. The anon key might carry over but the service-role key stays behind; the NEXT_PUBLIC_SUPABASE_URL is typed by hand and has a trailing slash; STRIPE_WEBHOOK_SECRET was never in Lovable to begin with because Lovable pointed the webhook at itself. The client bundle now ships with undefined for half its secrets and silently crashes on first mount.
Even when the env is correct, the database-level config still leaks. Supabase tables created by Lovable almost always ship with RLS off or with a single permissive FOR ALL USING (true)policy that does nothing. Supabase’s own docs make RLS opt-in for compatibility reasons; the model’s training data biases strongly toward enabling RLS only when the founder asks. Nobody asks in a preview because everything works. The production anon key hits the same tables and — depending on whether any policy exists — either fails closed (blank UI, console 401) or fails open (users see each other’s rows). The second outcome is the one that ends up in The Register.
OAuth, Stripe, and CORS follow the same pattern. OAuth works in preview because the Supabase Site URL still points at a Lovable subdomain; the first login on your real domain redirects to that subdomain and loses the session. Stripe webhooks work in preview because the test-mode endpoint is the one Lovable wired up; the first live checkout on your domain has no matching endpoint and the subscription never activates. CORS works in preview because every request is same-origin; the first cross-origin call from your frontend to a Supabase Edge Function returns preflight failure because the function never wrote an Access-Control-Allow-Origin header.
“You will have a bug. Now the Disaster begins.”
Diagnose broken Lovable app by failure mode
Open DevTools → Console and match your error to a row.
| Symptom | Console / log signature | Root cause | Fix section |
|---|---|---|---|
| Blank page on load, white screen | Uncaught Error: supabaseUrl is required. | Env vars missing in production bundle | Step 1 |
| Signed-in user sees other users' rows | 200 OK on /rest/v1/<table>?select=* with too many rows | RLS disabled or FOR ALL USING (true) policy | Step 2 |
| Login redirects to localhost or .lovable.app | redirect_uri_mismatch in URL bar after Google sign-in | Supabase Site URL + Google OAuth client still preview | Step 3 |
| Checkout succeeds, plan never activates | Stripe Dashboard shows webhook 404 or no delivery at all | Webhook endpoint wrong or test-mode secret in prod | Step 4 |
| Edge Function call returns CORS error | Access to fetch at '…/functions/v1/…' blocked by CORS policy | Edge Function missing OPTIONS handler and CORS headers | Step 5 |
broken Lovable app by AI builder
How often each AI builder ships this error and the pattern that produces it.
| Builder | Frequency | Pattern |
|---|---|---|
| Lovable | Every production deploy | Preview uses service_role; RLS off by default; webhook wired to preview subdomain |
| Bolt.new | Every StackBlitz export | WebContainer-only storage vanishes on deploy; webhook URL still stackblitz.io |
| v0 | Common | No backend scaffolded; founder bolts Supabase on later without RLS audit |
| Cursor | Sometimes | Fixes regress working code; no CI so RLS policies drift between branches |
| Replit | Common | Replit-hosted preview swaps secrets behind the scenes; real host sees undefined |
Related errors we fix
Stop broken Lovable app recurring in AI-built apps
- →Ship a zod schema on process.env that fails the build if any required key is undefined — catches env drift before deploy.
- →Make RLS a migration, not a dashboard click. Every table gets four policies (SELECT/INSERT/UPDATE/DELETE) checked into source control.
- →Write a pgTAP test asserting cross-user writes return 42501; run it in CI so a future policy edit cannot silently open access.
- →Use Stripe CLI in CI: `stripe trigger checkout.session.completed` against the preview URL. Any regression in raw-body handling shows up before merge.
- →Treat the Lovable preview as a demo only. Always test prod behaviour in an incognito window against the real domain before telling users it's live.
Still stuck with broken Lovable app?
Five patterns cover the vast majority of broken Lovable apps. If yours is the rare outlier, we’ll find it on the free diagnostic.
- →Stuck for more than 4 hours
- →Paying users affected right now
- →Tried two fixes, broke a third thing
broken Lovable app questions
My Lovable app was working yesterday and now it's broken. What changed?+
Why does my Lovable app work for me but not other users?+
How do I know if it's a Lovable bug or my bug?+
Can I just redeploy to fix a broken Lovable app?+
How much does it cost to fix a broken Lovable app?+
Is it worth rescuing a broken Lovable app or should I rewrite?+
Ship the fix. Keep the fix.
Emergency Triage restores service in 48 hours. Break the Fix Loop rebuilds CI so this error cannot ship again.
Hyder Shah leads Afterbuild Labs, shipping production rescues for apps built in Lovable, Bolt.new, Cursor, Replit, v0, and Base44. our rescue methodology.
broken Lovable app experts
If this problem keeps coming back, you probably need ongoing expertise in the underlying stack.