Artículo
El login de Supabase redirige a localhost tras publicar: los cuatro ajustes que hay que corregir
Publicado el
La app ya está publicada en tu dominio. Un usuario se registra, pulsa el
enlace de confirmación del correo y aterriza en http://localhost:3000 con
un "No se puede acceder a este sitio". O termina el login con Google y le
devuelve a localhost:8080, o a la preview de Lovable, o a la dirección
lovable.app en lugar de a tu dominio.
Al navegador del usuario no le pasa nada. Supabase Auth decide a dónde mandar
a alguien después del login a partir de cuatro ajustes, y al menos uno sigue
apuntando al sitio donde se construyó la app. El valor por defecto de la Site
URL en Supabase es http://localhost:3000, y por eso esa dirección aparece
incluso en apps que nunca han corrido en el puerto 3000.
Los cuatro sitios, en el orden en que conviene revisarlos:
- La Site URL en los ajustes de autenticación.
- La lista de Redirect URLs permitidas, justo al lado.
- La dirección de redirección que pasa el código cuando inicia un login.
- Las plantillas de correo, más los correos que ya se han enviado.
El login con Google añade un quinto, la consola de Google Cloud, que está al final.
Cómo elige Supabase la dirección
Cuando tu código inicia un login, puede pasar una dirección de redirección:
redirectTo para signInWithOAuth y resetPasswordForEmail,
emailRedirectTo para signUp y signInWithOtp. Supabase manda al usuario
ahí solo si la dirección coincide con una entrada de la lista de Redirect
URLs. Si el código no pasa nada, o pasa algo que no está en la lista, el
usuario va a la Site URL.
Así que "siempre va a localhost" casi siempre significa una de dos cosas: la Site URL sigue siendo localhost y el código no pasa nada, o el código pasa una dirección de producción que no está en la lista y Supabase cae en una Site URL que sigue siendo localhost.
1. Pon tu dominio real en la Site URL
La Site URL es el destino por defecto y la base de los enlaces de los correos
de autenticación. Tiene que ser tu dirección de producción con el protocolo y
el host exactos que ven los usuarios: aquí https://www.example.com y
https://example.com son direcciones distintas.
Dónde está depende de cómo esté montado el backend del proyecto:
- Tu propio proyecto de Supabase (Lovable conectado a Supabase, Bolt, v0 o cualquier app hecha a mano): panel de Supabase, Authentication, URL Configuration.
- Lovable Cloud: More, luego Cloud, luego Users, luego Auth settings, en
Advanced. La documentación de Lovable dice que pone como Site URL tu
dirección
lovable.appal publicar, y que añade el dominio personalizado a las Redirect URLs cuando lo conectas. No toca una Site URL que hayas puesto tú. Así que si los usuarios acaban en la direcciónlovable.appen vez de en tu dominio, pon aquí el dominio personalizado como Site URL.
Las apps con su propio proyecto de Supabase no tienen nada de esa automatización; la documentación de Lovable lo dice explícitamente. Cada dirección hay que añadirla a mano.
2. Mete todas las direcciones reales en la lista de Redirect URLs
La lista tiene que contener cada dirección a la que tu app devuelve a los
usuarios. Supabase admite comodines: * coincide con cualquier cosa menos
. y /, mientras que ** coincide con cualquier cosa, barras incluidas.
Una lista razonable para una app típica:
https://www.example.com/**
https://example.com/**
http://localhost:8080/**
La última línea mantiene funcionando el desarrollo en local; usa el puerto de
tu servidor de desarrollo (los proyectos Vite de Lovable usan el 8080, una
app Vite normal el 5173, Next.js el 3000). Supabase recomienda usar ** para
las direcciones locales y de preview y ser más exacto en producción, por
ejemplo https://www.example.com/auth/callback si es tu única ruta de
vuelta. Un comodín amplio en producción no es un agujero por sí solo, pero
con una ruta exacta es más fácil saber qué está permitido.
Para las preview deployments de Vercel, Supabase documenta un patrón como
https://*-your-team.vercel.app/**, para que las previews puedan hacer login
sin que tengas que añadir cada una.
3. Busca el localhost escrito a fuego en el código
Las herramientas de IA suelen escribir la dirección de redirección como un
literal mientras construyen en la preview, y sobrevive a todos los cambios
posteriores. Busca en el código localhost, redirectTo, emailRedirectTo
y el hostname de la preview. Un culpable típico:
await supabase.auth.signInWithOAuth({
provider: 'google',
options: { redirectTo: 'http://localhost:8080/dashboard' },
})
En una app que solo corre en el navegador (Vite, React), construye la dirección a partir de donde está realmente el usuario:
await supabase.auth.signInWithOAuth({
provider: 'google',
options: { redirectTo: `${window.location.origin}/dashboard` },
})
y lo mismo con emailRedirectTo en signUp y redirectTo en
resetPasswordForEmail. En apps renderizadas en servidor (Next.js), window
no existe en el servidor, así que lee la dirección de una variable de entorno
como NEXT_PUBLIC_SITE_URL y ponla en cada entorno. La documentación de
Supabase sugiere justo eso para Vercel.
Lo que produzca el código tiene que coincidir con una entrada de la sección 2, o Supabase cae en silencio a la Site URL y vuelves a estar donde empezaste.
4. Arregla las plantillas de correo y manda correos nuevos
Los correos de confirmación, magic link, invitación y cambio de contraseña se
generan a partir de plantillas en Authentication, luego Emails (Email
Templates en los paneles antiguos). Las plantillas por defecto usan
{{ .ConfirmationURL }}, que ya incluye la dirección de redirección. Las
plantillas personalizadas a veces construyen el enlace con {{ .SiteURL }},
que ignora emailRedirectTo por completo. La documentación de Supabase dice
que, cuando pasas una dirección de redirección, puede que tengas que cambiar
{{ .SiteURL }} por {{ .RedirectTo }} en las plantillas.
Y luego el paso que más tiempo hace perder: los correos ya enviados conservan el enlace antiguo. Después de cambiar cualquiera de estos ajustes, prueba con un registro nuevo o una petición de cambio de contraseña nueva, no con el correo de hace una hora.
5. Login con Google: la parte de Google Cloud
Con tu propio cliente OAuth, Google necesita conocer dos direcciones, y son distintas:
- Authorized redirect URIs: el callback de Supabase, no tu web. En un
proyecto de Supabase alojado tiene esta pinta:
https://<project-ref>.supabase.co/auth/v1/callback, y el valor exacto aparece en el panel de Supabase, en la página del proveedor Google. Google redirige a Supabase; Supabase redirige después a tu app usando las secciones 1 a 3. - Authorized JavaScript origins: las direcciones de tu web, incluido el dominio personalizado.
Un error redirect_uri_mismatch de Google significa que la primera lista
está mal. Una pantalla de Google que va bien seguida de una página de
localhost significa que Google está bien y el problema está en las secciones
1 a 3. En Lovable Cloud con el login de Google gestionado por Lovable, Lovable
se encarga de la parte de Google; si cambiaste a tus propias credenciales, la
documentación de Lovable avisa de que cada dominio nuevo también necesita su
redirect URI añadida en Google Cloud.
La redirección llega a tu dominio y da un 404
Una última variante: el usuario llega al dominio correcto, por ejemplo
https://example.com/auth/callback?code=..., y le sale un 404. Eso no es
Supabase. La app es una single-page app, el hosting estático no tiene ningún
archivo en /auth/callback y nadie ha configurado el hosting para servir
index.html en todas las rutas. En Vercel se arregla con un vercel.json
con un rewrite:
{ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] }
Netlify usa un archivo _redirects con /* /index.html 200. Si la página
carga pero el usuario sigue sin sesión, comprueba que el cliente se crea con
el detectSessionInUrl por defecto, que lee el código de la dirección y
termina el login.
Pruébalo como un usuario
Abre una ventana privada en un dispositivo que nunca haya visto la preview,
regístrate con una dirección nueva, pulsa el correo, cierra sesión, entra con
Google y cambia la contraseña. Cada flujo tiene que terminar en tu dominio
con el usuario dentro. Después hazlo una vez con www y otra sin él, porque
una sesión creada en un host no se ve en el otro.
Si todo eso pasa y los usuarios con sesión se encuentran errores de permisos al guardar por primera vez, la siguiente parada es el error "new row violates row-level security policy", que a menudo resulta ser una sesión que no existe.
Las redirecciones del login son uno de los fallos típicos al pasar una app de Lovable de la preview a producción. Si ya has arreglado esto y se te han roto otras dos cosas, probablemente la app necesite que alguien la lea de principio a fin. Eso es el diagnóstico de nuestro rescate de apps hechas con IA: 450 €, 48 horas laborables y un informe por escrito de qué está roto y cuánto cuesta cada arreglo. Si sigues con el rescate (entre 1.500 € y 6.000 €, con precio cerrado por alcance), los 450 € se descuentan.