Docs/Feature
// feature
Magic link
Il magic link è il secondo metodo di login già montato: l'utente scrive la sua email, riceve un link, ci clicca ed è dentro. Nessuna password da salvare, nessun form di recupero password da costruire, nessun hash da gestire. Sotto usa signInWithOtp di Supabase.
Il form in app/signin/page.tsx
Stessa funzione handleSignup del login Google, ma con type: “magic_link”. Cambia il nome dell'opzione di redirect: qui è emailRedirectTo, non redirectTo.
const redirectURL = window.location.origin + "/api/auth/callback";
await supabase.auth.signInWithOtp({
email,
options: {
emailRedirectTo: redirectURL,
},
});
toast.success("Controlla la tua email!");
setIsDisabled(true);Dopo l'invio il bottone si disabilita (isDisabled) e compare un tag “Controlla la casella email — magic link inviato”. Il toast arriva dal <Toaster /> montato in components/LayoutClient.tsx, quindi toast() funziona da qualsiasi Client Component senza altro setup.
Dove atterra il link
Il link nell'email non punta al tuo sito: punta a Supabase, che verifica il token e poi rimbalza su emailRedirectTo — cioè la stessa route /api/auth/callback usata da Google. Da lì exchangeCodeForSession crea la session e ti manda su config.auth.callbackUrl. Un solo callback per entrambi i metodi di login, niente codice duplicato.
Anche qui vale la allowlist: Authentication → URL Configuration → Redirect URLs deve contenere http://localhost:3000/api/auth/callback e la versione di produzione. Se manca, il link nell'email ti scarica sulla home senza sessione.
Chi manda davvero l'email
Non libs/resend.ts. Il magic link lo spedisce Supabase, con il suo SMTP di default. Quel server condiviso è pesantemente rate-limited (poche email all'ora, e solo verso indirizzi del team del progetto): va benissimo per provare, non regge nemmeno il primo giorno di lancio.
La soluzione è collegare un SMTP tuo. Dashboard Supabase → Authentication → Emails → SMTP Settings: attiva Enable Custom SMTP e metti host smtp.resend.com, porta 465, username resend, password la tua API key Resend. Nel campo Sender usa l'indirizzo che hai già scritto in config:
resend: {
// usato come mittente dei magic link — lo incolli nelle SMTP Settings di Supabase
fromNoReply: `ZeroToShip <noreply@resend.zerotoship.com>`,
fromAdmin: `Alberto at ZeroToShip <alberto@resend.zerotoship.com>`,
supportEmail: "tuaemail@example.com",
},Da sapere: config.resend.fromNoReply nel repo non è letto da nessuna riga di codice — sendEmail() in libs/resend.ts usa sempre fromAdmin. È un promemoria in config del mittente da configurare lato Supabase. Il dominio va verificato su Resend prima: vedi Email (Resend).
Personalizzare il testo dell'email
Il template vive nella dashboard Supabase (Authentication → Emails → Magic Link), non nel repo. È HTML normale con la variabile {{ .ConfirmationURL }} per il link. Cambia oggetto e copy in italiano — il default inglese di Supabase è la cosa che fa più “template non finito” di tutte.
Gestire gli errori dell'invio
Nel repo il catch di handleSignup fa solo un console.log(error): se Supabase rifiuta l'invio (rate limit, email non valida) l'utente vede comunque il toast di successo. signInWithOtp inoltre non lancia: ritorna un oggetto con error dentro. Se vuoi il feedback corretto, leggilo:
// Non è ancora nel repo: aggiungilo per mostrare gli errori reali
const { error } = await supabase.auth.signInWithOtp({
email,
options: { emailRedirectTo: redirectURL },
});
if (error) {
toast.error(error.message);
return;
}
toast.success("Controlla la tua email!");
setIsDisabled(true);Il pattern generale per mostrare errori all'utente è in Gestione degli errori.