Supabase redirectTo not working on Vercel previews: SITE_URL fix
nextjs-supabase

Supabase redirectTo not working 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.

2026-06-27
6 min read
Supabase redirectTo not working on Vercel previews: SITE_URL fix

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.

js
// 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):

plaintext
https://yourdomain.com

This 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):

plaintext
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:

js
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:

js
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:

html
<!-- 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 }}:

html
<!-- 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_URL set 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).

Frequently Asked Questions

|

Have more questions? Contact us

Written by

Mahdi Br
Mahdi Br

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

One email a month — no fluff

RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.