Skip to content
VibePatchers

Fix guide ·

Supabase login redirects to localhost or your preview URL after publishing

This one shows up constantly from people who just published a Lovable, Bolt, Replit or v0 app. Login works fine in the preview. Then on the real domain, Google sign-in bounces back to http://localhost:3000, confirmation emails open the old preview link, or users get stuck in a login loop.

The app code is usually fine. Supabase decides where to send people after login, and it only sends them to addresses you’ve told it about. Out of the box, the fallback address is http://localhost:3000.

This guide assumes your app talks to your own Supabase project. If you’re on a builder’s built-in backend (for example Lovable Cloud) rather than a Supabase project you can open yourself, look for the same Site URL and redirect settings in that builder’s auth settings.

Why it happens

There are two redirect hops in a typical login, and either one can still point at the old address:

  1. Provider to Supabase. Google, GitHub and other OAuth providers send the user back to your Supabase project’s callback URL (https://<project-ref>.supabase.co/auth/v1/callback). That’s configured at the provider.
  2. Supabase to your app. Supabase then sends the user to the redirectTo address your code asked for, but only if that address is on the project’s Redirect URLs allow list. If it isn’t, Supabase quietly falls back to the Site URL.

So when the Site URL is still http://localhost:3000 (the default) or your preview link, and your live domain was never added to the allow list, every login ends up there.

1. Set the Site URL to your live domain

In the Supabase dashboard, go to Authentication → URL Configuration and set Site URL to the address real users visit:

https://yourapp.com

No trailing path. This is also the default destination used in emails like signup confirmation, so getting it right fixes a lot of “the link opens the preview” reports on its own.

2. Add every address you use to Redirect URLs

On the same page, add each place users should be able to land after login. Exact URLs are the most predictable:

https://yourapp.com/**
https://www.yourapp.com/**
http://localhost:5173/**

Add your preview domain too if you still test there. Supabase allows wildcards (* matches within one part of the address, ** matches across slashes), which is handy for preview deploys that get a new subdomain each time. For your production domain, prefer the most specific pattern that works, such as https://yourapp.com/auth/callback, so the allow list can’t be used to send users somewhere you didn’t intend.

3. Stop hard-coding the redirect in your code

AI-generated auth code often bakes in whatever address the app had when the code was written. Search the project for it:

grep -rnE "localhost|lovable\.app|lovableproject\.com|bolt\.new|replit\.dev|vercel\.app" src/

Then build the redirect from wherever the app is running right now:

// OAuth (Google, GitHub, ...)
await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: { redirectTo: `${window.location.origin}/auth/callback` },
});

// Email + password signup confirmation
await supabase.auth.signUp({
  email,
  password,
  options: { emailRedirectTo: `${window.location.origin}/auth/callback` },
});

// Magic link
await supabase.auth.signInWithOtp({
  email,
  options: { emailRedirectTo: `${window.location.origin}/auth/callback` },
});

// Password reset
await supabase.auth.resetPasswordForEmail(email, {
  redirectTo: `${window.location.origin}/reset-password`,
});

Whatever path you use here (/auth/callback, /reset-password) has to exist as a real route in your app, and it has to match an entry in Redirect URLs.

If your app reads the site address from an environment variable such as VITE_SITE_URL, remember that Vite bakes it in at build time. Changing it in your host’s settings does nothing until you rebuild and redeploy.

4. Check the OAuth provider’s callback

If Google shows Error 400: redirect_uri_mismatch, the problem is the first hop, not Supabase’s allow list.

  • Google: in Google Cloud Console, open APIs & Services → Credentials, edit your OAuth client, and make sure Authorized redirect URIs contains your Supabase callback, exactly as shown on the Google provider page in Supabase (Authentication → Providers → Google). Add your live domain under Authorized JavaScript origins.
  • GitHub: in your OAuth app settings, Authorization callback URL should be the same Supabase callback URL.

The provider never needs your app’s URL as the callback. It sends users to Supabase, and Supabase sends them to you.

If you moved Supabase to a custom auth domain, the callback changes to that domain, so update it at the provider as well.

5. Check your email templates

Under Authentication → Emails (templates), confirmation, magic link, invite and reset-password emails should use Supabase’s template variables, such as {{ .ConfirmationURL }}, rather than a pasted link. A template someone customized during the demo phase may still contain the preview address in plain text.

6. Test it like a real user

On the live domain, in a private browser window:

FlowWhat should happen
Sign up with emailConfirmation email link opens your live domain and you’re logged in
Log in with each OAuth providerYou land back on the live domain, not localhost or preview
Magic link (if used)Opens the live domain and logs you in (open it in the browser you requested it from)
Reset passwordReset link opens your live /reset-password page
Log out, log back inNo loop back to the login screen

Common gotchas

  • The change didn’t seem to work. Old emails keep their old links. Request a new confirmation or magic link after changing settings.
  • Magic link fails when opened on another device. If your app uses the PKCE sign-in flow (the default in Supabase’s server-side helper, @supabase/ssr), the link has to be opened in the same browser that requested it. Requesting it on your laptop and opening it on your phone can fail even with perfect redirect settings.
  • www vs no www. https://yourapp.com and https://www.yourapp.com are different addresses to Supabase. Allow both, or redirect one to the other at your host.
  • Login loop on the live domain only. Usually the redirect landed on a route that doesn’t finish the sign-in (no /auth/callback handler), or the app checks for a session before the client has processed the login. Confirm the callback route exists in the deployed build.
  • Mobile apps (Expo, Capacitor). The redirect is your app’s custom scheme (for example myapp://auth/callback), and that scheme must be in Redirect URLs too.

Quick checklist

CheckWhere
Site URL is the live domainSupabase → Authentication → URL Configuration
Live, www, preview and localhost addresses allowedSame page, Redirect URLs
No hard-coded localhost or preview linksgrep your src/ folder
Provider callback is the Supabase /auth/v1/callback URLGoogle Cloud Console / GitHub OAuth app
Email templates use variables, not pasted linksSupabase → Authentication → Emails
Every flow tested on the live domainPrivate window

Not sure what your published app is exposing while you’re in there? The free app scanner checks a live URL for secret-looking keys in its JavaScript. And if login is one of several things broken after publishing, a flat-fee Rescue fixes auth along with the rest and proves it with a before/after verification report.

Rather have it fixed and proven?

Our flat-fee Rescue fixes this and hands you a before/after verification report within 48 hours of working access.

See pricing