You build an app in Lovable, Bolt, v0, or Cursor. Login works perfectly on localhost. You deploy to Vercel, log in on the live site, and you are immediately bounced back to the login page. Or the page loads but every query returns nothing, as if you are a stranger to your own app.
This is the single most common way an AI-built Supabase app breaks in production. It is almost never the login code itself. It is the session, and specifically where the session lives.
Here is what is actually happening, and the order to check it in.
The session lives in cookies, and cookies are the thing that breaks
Supabase auth gives you a session and stores it in cookies. Every part of your app that talks to Supabase has to read those cookies correctly. On localhost, a lot of sloppiness is survivable: one origin, no HTTPS, no edge, no separate server context. In production, all of that becomes strict.
So the question is never "is my login broken." The question is "which layer lost the cookie."
1. You are using the browser client on the server
This is the number one cause in generated code.
@supabase/supabase-js createClient is a browser client. It keeps the session in browser storage. If you call it from a server component, a route handler, or middleware, there is no browser, so there is no session. Your query runs anonymous, RLS says no, and you get empty results or a redirect.
In an App Router project you need two different clients from @supabase/ssr:
- a browser client for client components
- a server client, created per request, that reads and writes cookies
If your project has a single supabase.ts that everything imports, that is your bug. Generated code does this constantly because it works locally.
2. Middleware is not refreshing the session
Supabase access tokens expire. @supabase/ssr expects middleware to refresh the token and write the updated cookies back onto the response.
If you have no middleware, or middleware that reads cookies but never sets them back, the session silently dies the moment the token expires. Classic symptom: login works, then a refresh five minutes later logs you out.
Your middleware must both refresh the session and return the response carrying the updated cookies. If your middleware creates a response object and then returns a different one, you dropped the cookies.
3. cookies() is not awaited
On recent Next.js versions cookies() is async. If your generated code calls it synchronously, you can get an app that builds fine and behaves strangely at runtime, because the cookie store is not what you think it is.
Search your project for cookies() and make sure every call is awaited, and that the server client is created inside the request scope rather than at module top level. A Supabase server client created at module scope is shared across requests, which is both a bug and a security problem.
4. Your production URL is not in Supabase's redirect allowlist
Open your Supabase dashboard, Authentication, then URL Configuration. Site URL and Redirect URLs must include your real deployed domain, including the exact protocol and any preview domains you actually use.
If the allowlist only has localhost, the auth callback on production is rejected. Supabase is doing the right thing. It just looks like your login is broken.
Add the production domain. If you use Vercel preview deployments, be aware their URLs change per deploy, which is a common source of "it works on prod but not on preview."
5. Cookie flags differ between localhost and HTTPS
On localhost you are on http and one origin. In production you are on https, possibly with a custom domain. Cookies marked Secure will not be set over plain http, and SameSite rules bite the moment an OAuth provider redirects you back from another origin.
If you hand-rolled cookie options in generated code, this is worth a look. If you let @supabase/ssr manage them, it is usually correct.
How to check this in the right order
Do not start changing code. Start by finding out who Supabase thinks you are.
- On the deployed site, log in, then hit a server route that logs the result of
getUser(). If it returns null, your server has no session and the cause is in items 1 to 3. - If
getUser()returns your user but queries are still empty, your session is fine and your problem is RLS, not auth. Different bug, different article. - Check the Supabase dashboard redirect allowlist against the exact domain in your address bar.
- Log in and wait past token expiry, then refresh. If that is when it breaks, your middleware is not refreshing.
That sequence tells you which layer lost the cookie in about five minutes, without touching a line of code.
Why AI builders produce this specific bug
Generated code optimizes for the preview working. The preview is a single origin, no HTTPS boundary, no expiry, no separate server context. Every shortcut that fails in production is invisible there. The tool did not lie to you. It just never had to cross the boundary that production forces.
This is why these apps break on deploy specifically, and why the fix is almost always at a boundary rather than in the feature logic.
When to stop guessing
If you have changed the same three files four times and the behavior keeps moving, stop. Auth, RLS, cookies, and deployment callbacks interact, and guessing changes more than one variable at a time. Preserve the exact error, find the failing boundary, then make the smallest change that crosses it.
If you want a second set of eyes, we run a free diagnosis for broken AI-built apps: describe what is broken in plain words, or paste the error if you have one, and you get the likely cause and a fix path back. No card, no repo access to start. https://rescue.ticassociation.com
A TIC Association creation.
Top comments (0)