Supabase Auth Redirect Doctor · free, client-side

Supabase OAuth redirects to localhost in production?

Paste your Next.js, Vite, or SvelteKit + Supabase Auth redirect config and get exact mismatch fixes before wasting another afternoon.

Runs entirely in your browser. No account, no backend, no payment.

Three reasons your OAuth flow is silently broken.

Every Supabase Auth team hits the same three walls in production. None of them show up until deploy day, because locally they all happen to line up by accident.
P01 · site url

OAuth redirects to localhost even in production.

Supabase falls back to its Site URL whenever redirectTo is missing or not on the allow-list — and Site URL still says http://localhost:3000 because nobody changed it after the project was created.

silent fallback
P02 · allow-list

www vs apex, trailing slashes, preview URLs.

The Redirect URLs allow-list matches by glob, not by domain equivalence — an entry for www.example.com does not cover the apex domain, a trailing slash breaks an exact match, and Vercel/Netlify preview URLs need their own pattern.

glob mismatch
P03 · callback

Missing PKCE route, or the provider console points at your app.

PKCE needs a server route that calls exchangeCodeForSession() — miss it and the flow dies with a code in the URL nobody reads. And Google/GitHub want the Supabase callback URL, not yours.

three-way mismatch

Fill in your config. Get the exact mismatches.

Nothing you type is sent anywhere. The check runs in your browser, using the same allow-list matching rules Supabase documents.
fn RedirectDoctorWeb.diagnose(config)
client-side · no network
App
Supabase
OAuth provider
Your code
Result · idle
# fill in the form and press Diagnose or ⌘↵
status
idle

One function. Config in, diagnosis out.

No server, no SDK key, no signup. doctor-web.js is plain JavaScript — read it, fork it, or call it directly from your own scripts or CI.
doctor-web.js 0 dependencies
import { diagnose } from './doctor-web.js';
// or, loaded globally: const { diagnose } = window.RedirectDoctorWeb;

const result = diagnose({
  app:      { productionOrigin, callbackPath, framework, usesSsrPackage, flowType, envSiteUrlVar, deployedOn },
  supabase: { projectUrl, siteUrl, allowedRedirectUrls },
  provider: { name, authorizedRedirectUris },
  code:     { redirectToSnippet, callbackRouteExists, callsExchangeCodeForSession },
});

// result
{
  "status":     "fail" | "warn" | "pass",
  "summary":    "…",
  "expected":   { siteUrl, callbackUrl, allowListEntries, supabaseCallback, previewPattern },
  "problems":   [ { severity, code, message, where } ],
  "fixes":      [ { title, value, where } ],
  "checklist":  [ "…" ],
  "disclaimer": "…"
}

Everything runs client-side. The form above calls this exact function in your browser. There is no backend, no API key, and no request that carries your config anywhere.

It checks the callback URL your app will actually use against your Supabase redirect allow-list (glob-matched the same way Supabase matches it), your Site URL, and the exact callback your OAuth provider console expects.

// free, open, and demand-gated for what comes next.
// found a case it gets wrong? open a GitHub issue.
// no ads, no tracking beyond anonymous usage counts.

Free while in beta.

No account, no payment, no usage limit. If that ever changes, this version stays free for the people already using it.
Free — unlimited diagnoses, no signup, no rate limit.

This started as a tool I needed myself. If it saves you an afternoon, the best way to say thanks is to tell me what it got wrong — open a GitHub issue.

!

Read-only, client-side analysis. Nothing you enter is sent anywhere — the check runs entirely in your browser against the values you typed, not your live project. Always verify in your own environment. Not affiliated with Supabase, Vercel, Netlify, Google, GitHub, Microsoft, or Discord.

Questions developers actually search for.

Straight answers to the same Supabase Auth redirect questions this tool diagnoses — for when you just need the answer, not the checker.
Q01

Why does Supabase OAuth redirect to localhost after deploying to Vercel?

Supabase falls back to your project's Site URL whenever the redirectTo value your code sends doesn't exactly match an entry on the Redirect URLs allow-list. If Site URL was never changed from its default of http://localhost:3000, any request that fails that allow-list check — a missing entry, a trailing slash, http vs https — lands back on localhost even though the app is live on Vercel.

Q02

What should Site URL be in Supabase for a Next.js app?

Site URL should be your app's canonical production origin — e.g. https://app.example.com — with no trailing slash and no path. It is the fallback Supabase uses whenever redirectTo doesn't match the allow-list, so leaving it at the default localhost value after deploying is what causes production sign-in to bounce back to localhost.

Q03

Which redirect URL do I add to Supabase for Vercel preview deployments?

Add a wildcard allow-list entry such as https://*.vercel.app/** alongside your production URL, since every preview deployment gets its own subdomain that a single hardcoded entry won't match. Without a wildcard pattern, OAuth on preview branches will keep falling back to Site URL even though production works fine.

Q04

Do I need exchangeCodeForSession in my /auth/callback route?

Yes, if your app uses the PKCE flow, which is the default for @supabase/ssr. The provider redirects back with a ?code= query parameter, and your callback route must call exchangeCodeForSession(code) server-side to exchange it for a session — without that call the code sits unused in the URL and the user is never actually signed in.

Q05

What redirect URI goes into the Google Cloud console for Supabase?

Google Cloud Console — and any other OAuth provider console — needs Supabase's own callback URL, https://<your-project-ref>.supabase.co/auth/v1/callback, not your app's domain or your /auth/callback route. Supabase is the OAuth client that talks to Google directly; your app's callback route only ever receives the redirect from Supabase afterward.

Q06

Why does process?.env.NEXT_PUBLIC_SITE_URL break my redirectTo?

Next.js inlines NEXT_PUBLIC_* variables at build time by statically matching the literal process.env.NEXT_PUBLIC_SITE_URL expression in your source code. Optional chaining, as in process?.env?.NEXT_PUBLIC_SITE_URL, doesn't match that pattern, so the bundler can't inline it — in the browser bundle it silently evaluates to undefined instead of your URL, sending redirectTo down the Site URL fallback path instead of your real domain.

Stop debugging redirect URLs at 2am.

Paste your config, get the exact mismatch, fix it, ship it — free, and it never leaves your browser.