Supabase redirectTo Failing on Vercel Previews: SITE_URL Fix
The redirect lands on your production domain instead of the preview URL, or you get a "redirect URL not allowed" error. Here is the exact Supabase dashboard config and the Vercel env var pattern.
This is the auth bug that only appears when a teammate opens a Vercel preview link and tries to log in. The error from Supabase: redirect_uri_mismatch or the redirect silently goes to production instead of the preview.
I hit this while QA-ing an OAuth flow. Everything worked fine on production. Preview deployments got a blank screen after the OAuth callback — because Supabase was sending the user to https://myapp.com/auth/callback instead of https://myapp-git-feature-team.vercel.app/auth/callback.
How Supabase redirect URLs work#
Supabase maintains an allowlist of permitted redirect destinations. When you pass a redirectTo in your auth calls, Supabase validates it against the list. If the URL is not in the list, the request fails.
// This redirectTo will fail if the current preview URL is not in the allowlist
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
})window.location.origin on a preview deployment returns something like https://myapp-git-feature-branch-myteam.vercel.app — which does not match any pattern in the list.
The dashboard config#
Location: Supabase Dashboard → Authentication → URL Configuration
You need three things configured:
Site URL (single value):
https://yourdomain.comThis is the fallback URL for emails (confirmation, password reset) when no redirectTo is specified. In production this must be your real domain, not http://localhost:3000.
Redirect URLs (allowlist):
http://localhost:3000/**
https://yourdomain.com/**
https://*-your-team-slug.vercel.app/**The ** wildcard matches any sequence of characters. The pattern https://*-your-team-slug.vercel.app/** covers every preview deployment URL that Vercel generates for your team.
How to find your team slug: in Vercel, go to Settings → General → URL Slug. Your preview URLs follow the format <project>-<branch>-<slug>.vercel.app.
The environment variable pattern for Vercel#
The redirectTo in your code must be dynamic — it should use the actual URL of the current deployment, not a hardcoded production URL.
The recommended pattern from the Supabase docs:
export function getSiteURL() {
let url =
process?.env?.NEXT_PUBLIC_SITE_URL ??
process?.env?.NEXT_PUBLIC_VERCEL_URL ??
'http://localhost:3000/'
// Vercel provides NEXT_PUBLIC_VERCEL_URL without https://
url = url.startsWith('http') ? url : `https://${url}`
// Ensure trailing slash
url = url.endsWith('/') ? url : `${url}/`
return url
}Then use it when calling auth:
import { createClient } from '@/lib/supabase/server'
import { getSiteURL } from '@/lib/utils'
export async function signInWithGitHub() {
const supabase = await createClient()
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: `${getSiteURL()}auth/callback`,
},
})
if (data.url) redirect(data.url)
}In Vercel environment variables:
| Environment | NEXT_PUBLIC_SITE_URL |
|---|---|
| Production | https://yourdomain.com |
| Preview | (leave unset — falls back to NEXT_PUBLIC_VERCEL_URL) |
| Development | http://localhost:3000 |
NEXT_PUBLIC_VERCEL_URL is automatically set by Vercel on every deployment with the correct preview URL. You do not need to set it yourself — but you do need NEXT_PUBLIC_SITE_URL in production to override the Vercel URL with your custom domain.
Email templates: the hidden SITE_URL problem#
Password reset and magic link emails use your SITE_URL by default in the template:
<!-- Default template — uses SITE_URL, ignores redirectTo -->
<a href="{{ .SiteURL }}/auth/confirm?...">Confirm your email</a>If your SITE_URL is http://localhost:3000 (the Supabase default before you change it), production password reset emails will send users to localhost.
The fix: update your email templates to use {{ .RedirectTo }} instead of {{ .SiteURL }}:
<!-- Updated template — uses dynamic redirectTo -->
<a href="{{ .RedirectTo }}">Confirm your email</a>Location in Supabase Dashboard: Authentication → Email Templates
The trailing slash trap#
Supabase URL matching is exact. https://yourdomain.com and https://yourdomain.com/ are different entries.
If your redirectTo is https://yourdomain.com/auth/callback and your allowlist only has https://yourdomain.com (no trailing slash, no wildcard), it will fail.
The /** wildcard suffix is the safest way to avoid this: https://yourdomain.com/** matches any path on that domain including /auth/callback.
When the fix is not just a URL allowlist#
If previews work but production email redirects still go to the wrong URL, the problem is usually the email template using {{ .SiteURL }} instead of {{ .RedirectTo }} — not the allowlist. Allowlist errors return an explicit error. Wrong-URL-in-email is silent: the user just lands somewhere unexpected.
If window.location.origin in your getSiteURL() helper returns undefined or an unexpected value server-side (it is a browser API), that is why — use NEXT_PUBLIC_SITE_URL or NEXT_PUBLIC_VERCEL_URL env vars instead, which work on the server.
Checklist#
- Supabase Dashboard → Authentication → URL Configuration: Site URL set to production domain (not localhost).
- Redirect URLs allowlist includes
http://localhost:3000/**. - Redirect URLs allowlist includes
https://yourdomain.com/**. - Redirect URLs allowlist includes
https://*-your-team.vercel.app/**. -
NEXT_PUBLIC_SITE_URLset to production domain in Vercel production environment. - Email templates updated to use
{{ .RedirectTo }}not{{ .SiteURL }}. - Test: open a fresh Vercel preview URL, click "Forgot password", verify email link goes to the preview URL (not production).
Related#
- Supabase auth redirect fix — related redirect issues with email confirmation and OAuth.
- Supabase email confirmation fix — email template issues that break the confirmation flow.
- Magic link production checklist — covers the SITE_URL and email template setup for magic link auth.
- Deploying Next.js and Supabase to production — full production setup including environment variables per environment.
- Is It Safe to Expose Firebase API Key to the Public?
Frequently Asked Questions
One email a month — no fluff
RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.
Continue Reading
Supabase Auth Session Disappears on Refresh? Fix (2026)
Supabase auth sessions mysteriously disappearing after page refresh? Learn the exact cause and fix it in 5 minutes with this tested solution.
Supabase getClaims() vs getSession(): The Silent Auth Bug
Your auth looks correct but users can bypass it. The bug: getSession() in server code reads from local storage without revalidating the token against the Auth server.
Supabase Email Confirmation Not Sending: 5 Fixes (2026)
Email confirmations not sending from Supabase? Learn the exact causes and fixes for SMTP, template, and configuration issues in 10 minutes.
Browse by Topic
Find stories that matter to you.
