DEV Community

Cover image for Supabase confirmation or reset email opens localhost:3000? Here's the fix
Ahmed Hassan Yaseen
Ahmed Hassan Yaseen

Posted on Originally published at stacksmesh.com AI-assisted

Supabase confirmation or reset email opens localhost:3000? Here's the fix

A user signs up for your live app, clicks the confirmation link, and the browser says:

localhost:3000 refused to connect
Enter fullscreen mode Exit fullscreen mode

Password reset emails do the same. Nothing is wrong with your deploy. Supabase builds email links from the project's Site URL, and a new project starts with that set to http://localhost:3000. Set the Site URL to your live domain, add every address you redirect to under Redirect URLs, and pass a redirect address from the app that is on that list.

I see this a lot in apps built with Lovable, Bolt and Replit on Supabase, usually right after the first real users arrive.

Why it happens

  1. The Site URL is still the default. Every confirmation and reset link falls back to it, so they all point at localhost:3000.
  2. The redirect address isn't allowed. If the app asks Supabase to redirect somewhere that isn't on the Redirect URLs list, Supabase uses the Site URL instead.
  3. A customised email template uses {{ .SiteURL }}. That ignores the redirect you passed. Templates for redirect flows should use {{ .RedirectTo }}.

Find the cause

  1. Open the email and hover the button. Note the exact address it opens.
  2. In the Supabase dashboard, open Authentication > URL Configuration and read the Site URL and the Redirect URLs list.
  3. Find where your app calls signUp, resetPasswordForEmail or signInWithOtp and check which redirect address it passes, if any.

Fix 1: set the Site URL and the allow list

  • Set Site URL to your production address, for example https://app.example.com.
  • Add the exact pages you redirect to, such as https://app.example.com/auth/callback and https://app.example.com/reset-password.
  • Keep http://localhost:3000/** only if you still develop locally. Supabase recommends exact paths in production rather than wildcards.

Fix 2: pass the redirect address from the app

Build the address from the current site, so previews and production both work:

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

await supabase.auth.signUp({
  email,
  password,
  options: { emailRedirectTo: window.location.origin + '/auth/callback' },
});
Enter fullscreen mode Exit fullscreen mode

Whatever window.location.origin resolves to on each environment has to be on the Redirect URLs list, or Supabase falls back to the Site URL again.

Fix 3: check customised email templates

In Authentication > Email Templates, replace {{ .SiteURL }} with {{ .RedirectTo }} in the templates whose flow passes a redirect address.

Check the fix worked

  • Sign up with a fresh email address: the confirmation link opens your live site and signs the user in.
  • Request a password reset: the link opens your reset page on the live domain.
  • If you use a preview or staging address, repeat once there to confirm its pattern is allowed.

Stop it coming back

  • Put the Site URL and Redirect URLs on your launch checklist. Both are set per project, so a new project starts at the default again.
  • Set up a custom sending domain for auth emails before launch so they reach inboxes.

Quick answers

Do I need to redeploy after changing the Site URL? No. It's a project setting and new emails use it right away. Emails already sent keep their old links, so ask affected users to request a new one.

What about a mobile app? Use a deep-link address such as com.example.app://login-callback as the redirect and add it to the Redirect URLs list.

Source: Supabase: Redirect URLs.


I first published this fix on our site: Supabase confirmation or reset email links to localhost: the fix. To check your live app for exposed keys, missing security headers and broken basics in about a minute, run the free Launch Check.

I wrote this with AI assistance and reviewed it myself.

Top comments (0)