Menu
Intégration

Formulaire de contact Next.js : guide complet

Dans l’App Router, un composant qui utilise useState et onSubmit doit être un composant client. Il peut appeler l’API publique du formulaire sans exposer de clé serveur.

Créer mon formulaire gratuitement

Un exemple à copier

React / JSX
'use client';
import { useState, useRef } from 'react';
export default function Contact() {
  const [etat, setEtat] = useState('');
  const [busy, setBusy] = useState(false);
  const tentative = useRef({ contenu: '', id: '' });
  async function envoyer(event) {
    event.preventDefault();
    const form = event.currentTarget;
    if (!form.reportValidity()) return;
    const data = new FormData(form);
    const fields = { name: data.get('name'), email: data.get('email'), message: data.get('message') };
    const contenu = JSON.stringify(fields);
    if (contenu !== tentative.current.contenu) tentative.current = { contenu, id: crypto.randomUUID() };
    setBusy(true); setEtat("Envoi en cours…");
    try {
      const response = await fetch("https://formeo.me/api/submit", {
        method: 'POST', headers: { 'Content-Type': 'application/json', 'Idempotency-Key': tentative.current.id },
        body: JSON.stringify({ form_key: "YOUR_ACCESS_KEY", fields })
      });
      const result = await response.json();
      if (!response.ok) throw new Error(response.status === 429 ? "Limite atteinte. Réessayez plus tard." : "Envoi impossible. Vérifiez les champs et réessayez.");
      setEtat("Votre message a été enregistré. La livraison e-mail est suivie séparément.");
    } catch (error) { setEtat(error instanceof Error ? error.message : "Envoi impossible."); }
    finally { setBusy(false); }
  }
  return <form onSubmit={envoyer}>
    <label>Votre nom<input name='name' autoComplete='name' required /></label>
    <label>Votre e-mail<input name='email' type='email' autoComplete='email' required /></label>
    <label>Votre message<textarea name='message' required maxLength={5000} /></label>
    <button disabled={busy}>Envoyer le message</button>
    <p role='status' aria-live='polite'>{etat}</p>
  </form>;
}

La clé YOUR_ACCESS_KEY est un emplacement à remplacer par la clé publique de votre formulaire. Le code ne contient aucune clé serveur ni adresse de destination.

Composant client ou traitement serveur

Placez 'use client' au début du fichier Contact.jsx. La clé publique du formulaire peut apparaître dans le navigateur ; un secret Supabase, Stripe ou Brevo ne doit jamais être intégré au composant.

Une Server Action reste une option si vous souhaitez traiter des données sur votre propre serveur. Elle implique alors de gérer la validation, les destinations et l’exploitation. L’exemple ci-dessous utilise le backend partagé.

États et erreurs

Le bouton est désactivé pendant l’envoi. Une zone aria-live affiche le résultat. reportValidity contrôle les champs avant l’appel ; le serveur contrôle aussi le schéma publié.

L’identifiant de tentative reste identique si le visiteur reprend le même envoi après une erreur réseau. Une modification du contenu génère un nouvel identifiant.

Vérifier après déploiement

Intégrez le composant dans une page, puis testez depuis le domaine final. Si votre formulaire limite les origines, autorisez votre domaine et les previews que vous souhaitez réellement tester.

Une réponse acceptée apparaît dans la boîte de réception. Les notifications nécessitent une adresse vérifiée et la configuration d’envoi du service.

Essayer le modèle avant de créer un compte

Aperçu local : les essais restent dans ce navigateur. Aucun message ni e-mail n’est envoyé.

Contact HTML

Nom, e-mail et message : le nécessaire pour ouvrir la conversation.

1 / 1
Formeo

Personnaliser les champs et le style

HTML / CSS autonome
<!doctype html>
<html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Formulaire de contact</title>
<style>form[data-littlereply]{max-width:640px;margin:2rem auto;padding:1.5rem;font:16px/1.6 system-ui,sans-serif;color:#202033;background:white;border:1px solid #e4e4ee;border-radius:16px;box-sizing:border-box}form[data-littlereply] label{display:block;margin-top:1rem;font-weight:600}form[data-littlereply] input,form[data-littlereply] textarea,form[data-littlereply] select{box-sizing:border-box;width:100%;padding:.75rem;font:inherit;border:1px solid #727285;border-radius:8px;background:white;color:#202033}form[data-littlereply] input[type=checkbox],form[data-littlereply] input[type=radio]{width:auto}form[data-littlereply] button{margin-top:1rem;padding:.8rem 1.1rem;font:inherit;font-weight:600;border:0;border-radius:8px;background:var(--form-accent,#4f46e5);color:white;cursor:pointer}form[data-littlereply] :focus-visible{outline:3px solid #f59e0b;outline-offset:3px}form[data-littlereply] button:disabled{opacity:.6}form[data-littlereply] [role=status]{min-height:1.6em}form[data-littlereply] fieldset{border:0;padding:0}form[data-littlereply] .privacy-notice{font-size:.875rem;color:#4b4b60}
form[data-littlereply]{--form-accent:#4f46e5;--form-ink:#ffffff}
form[data-littlereply] button{color:var(--form-ink)}form[data-littlereply] button[data-fq-back]{background:#eee;color:#202033}form[data-littlereply] [hidden]{display:none}</style></head><body>
<form action="https://formeo.me/api/submit" method="POST" data-littlereply="YOUR_ACCESS_KEY" data-locale="fr" data-success="Votre message a bien été enregistré. Nous vous répondrons bientôt.">
  <input type="hidden" name="_fq_key" value="YOUR_ACCESS_KEY" />
  <input type="text" name="botcheck" tabindex="-1" autocomplete="off" style="display:none" aria-hidden="true" />

  <label for="fq_name">Votre nom *</label>
  <input type="text" name="name" id="fq_name" required autocomplete="name" />

  <label for="fq_email">Votre adresse e-mail *</label>
  <input type="email" name="email" id="fq_email" required autocomplete="email" />

  <label for="fq_message">Votre message *</label>
  <textarea name="message" id="fq_message" required maxlength="5000" rows="5"></textarea>

  <button type="submit">Envoyer mon message</button>
  <p role="status" aria-live="polite"></p>
</form>
<!-- Erreurs AJAX accessibles et reprises sans doublon -->
<script src="https://formeo.me/client/v1.js" data-api="https://formeo.me" defer></script>
</body></html>

Remplacez YOUR_ACCESS_KEY par la clé publique de votre propre formulaire. Le code inclut ses styles ; le script client ajoute les états AJAX. Le POST HTML fonctionne aussi sans JavaScript.

Sources

Questions fréquentes

Faut-il une route API Next.js ?

Pas pour cet exemple : le composant transmet directement au backend du formulaire.

Puis-je utiliser TypeScript ?

Oui. Ajoutez les types React.FormEvent<HTMLFormElement> au gestionnaire et adaptez les assertions FormData dans votre projet.

Passer de l’exemple à votre formulaire

Créer mon formulaire gratuitement