DEV Community

Mahdi BEN RHOUMA
Mahdi BEN RHOUMA

Posted on • Originally published at iloveblogs.blog

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.

// 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`,
  },
})
Enter fullscreen mode Exit fullscreen mode

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.com
Enter fullscreen mode Exit fullscreen mode

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

http://localhost:3000/**
https://yourdomain.com/**
https://*-your-team-slug.vercel.app/**
Enter fullscreen mode Exit fullscreen mode

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 filename="lib/utils.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 filename="app/auth/actions.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)
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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).

Related


Originally published at https://www.iloveblogs.blog

Top comments (0)