Vérification d'e-mails avec Clerk
Clerk gère des webhooks pour les événements du cycle de vie utilisateur, dont user.created. Clerk n'offre pas de blocage natif avant création, mais vous pouvez combiner un webhook et les métadonnées utilisateur pour marquer les inscriptions invalides en vue d'une suppression immédiate, ou employer le motif de redirection avant inscription de Clerk.
Ce tutoriel utilise le motif recommandé : vérifier l'adresse depuis un endpoint serveur avant d'appeler signUp.create de Clerk.
Prérequis
- Un compte Clerk et un projet Next.js avec
@clerk/nextjs - Une clé d'API Mailbeam (inscription gratuite)
Mise en place
npm install svix # svix est la bibliothèque de webhooks de ClerkEndpoint de vérification côté serveur
// app/api/verify-email/route.ts
import { NextResponse } from "next/server";
// Pas encore de SDK : voici le wrapper de 12 lignes de /docs/quickstart.
import { verifyEmail } from "./lib/mailbeam";
export async function POST(req: Request) {
const { email } = await req.json();
if (!email) {
return NextResponse.json({ error: "email required" }, { status: 400 });
}
try {
const { valid, score, reason } = await verifyEmail(email);
return NextResponse.json({ valid, score, reason });
} catch {
// Échec permissif
return NextResponse.json({ valid: true, score: 50, reason: null });
}
}Formulaire d'inscription sur mesure
// app/signup/CustomSignupForm.tsx
"use client";
import { useSignUp } from "@clerk/nextjs";
import { useState } from "react";
export function CustomSignupForm() {
const { signUp, setActive } = useSignUp();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setError(null);
// Étape 1 : vérifier l'adresse côté serveur avant d'appeler Clerk
const verifyRes = await fetch("/api/verify-email", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email }),
});
const { valid, score, reason } = await verifyRes.json();
if (!valid || score < 60) {
setError(
reason === "disposable_domain"
? "Merci d'utiliser une adresse e-mail permanente."
: "Merci de fournir une adresse e-mail valide."
);
setLoading(false);
return;
}
// Étape 2 : poursuivre l'inscription Clerk
try {
const result = await signUp!.create({ emailAddress: email, password });
if (result.status === "complete") {
await setActive!({ session: result.createdSessionId });
window.location.href = "/dashboard";
}
} catch (err: unknown) {
setError((err as { errors?: { message: string }[] })?.errors?.[0]?.message ?? "L'inscription a échoué.");
} finally {
setLoading(false);
}
}
return (
<form onSubmit={handleSubmit} className="space-y-4 max-w-sm">
{error && (
<p role="alert" className="rounded-lg bg-destructive/10 p-3 text-sm text-destructive">
{error}
</p>
)}
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Adresse e-mail"
className="w-full rounded-lg border border-border px-3 py-2 text-sm bg-background"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Mot de passe"
className="w-full rounded-lg border border-border px-3 py-2 text-sm bg-background"
required
/>
<button
type="submit"
disabled={loading}
className="w-full rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground disabled:opacity-60"
>
{loading ? "Création du compte…" : "Créer le compte"}
</button>
</form>
);
}Bonnes pratiques
- Vérifiez avant d'appeler Clerk, pas après : cela évite les utilisateurs orphelins non vérifiés
- Gardez l'endpoint de vérification léger : il ne doit qu'appeler Mailbeam et renvoyer le résultat
- Servez-vous de
reasonpour donner des messages d'erreur précis