Guides

Guides17 min de lecture

Ajouter un formulaire de contact à un site statique sans backend

Ajoutez un formulaire de contact fonctionnel à un site statique sans serveur : où va le POST, le spam, les réponses par e-mail, et du code pour chaque hébergeur.

Preet SutharFondateur, Ikiform

Un hébergeur statique sert des fichiers et refuse un POST de formulaire, généralement avec 405 ou 404 : le formulaire doit donc envoyer ailleurs. Faites pointer l'action du formulaire vers un backend de formulaires comme Ikiform, qui stocke chaque message, filtre le spam et vous l'envoie par e-mail avec l'expéditeur comme adresse de réponse. Votre site reste statique : pas de code serveur, pas de clé secrète dans la page et rien ne change dans votre déploiement.

Un site statique est un dossier de HTML, CSS et JavaScript qu'un hébergeur distribue sous forme de fichiers. C'est pour cela qu'il est rapide, bon marché et difficile à casser. C'est aussi pour cela que le formulaire de contact est la première chose qui cesse de fonctionner : une soumission de formulaire est une requête que quelque chose doit recevoir, et un hébergeur statique n'a rien à l'écoute.

Pourquoi le formulaire échoue sur un hébergeur statique

Un <form method="post"> sans action envoie vers la page où il se trouve. Sur un hébergeur statique, c'est un fichier, et les fichiers ne répondent qu'à GET et HEAD. Nous avons envoyé un POST à une page statique en ligne sur chacun des grands hébergeurs le 2026-10-25. Aucun n'a rien stocké :

HébergeurCe que reçoit un POST vers une page statique
GitHub Pages405 Not Allowed, sous forme de petite page HTML
Vercel405 avec un corps vide
Netlify404, avec la page 404 du site s'il en a une, sauf si Netlify Forms a détecté ce formulaire au déploiement
Cloudflare Pages405 avec un corps vide
Endpoint de site web Amazon S3405 Method Not Allowed, avec le code MethodNotAllowed

La solution est partout la même : envoyer vers une URL qui accepte le POST. Cette URL peut être du code que vous écrivez et hébergez, une fonctionnalité de votre hébergeur ou un backend de formulaires. La suite de ce guide utilise un backend de formulaires, car il garde le site statique.

Où va l'envoi d'un formulaire de contact, avant et après la correctionaction vers votre propre siteaction vers Ikiformpage de contactHTML statiquehébergeur statiquesert des fichiers,répond à GET405 ou 404message perdupage de contactmême HTML, nouvelleactionIkiformvérifie, stocke, envoiepage deremerciementretour sur votre sitePOSTPOST303Où va l'envoi d'un formulaire de contact, avant et après la correctionaction vers votre propre siteaction vers Ikiformpage de contactHTML statiquehébergeur statiquesert des fichiers, répond à GET405 ou 404message perdupage de contactmême HTML, nouvelle actionIkiformvérifie, stocke, envoiepage de remerciementretour sur votre sitePOSTPOST303
  1. Sans action, ou avec une action qui pointe vers un chemin de votre propre site, le navigateur envoie à l'hébergeur statique.
  2. L'hébergeur n'a aucun code à exécuter pour un POST : il répond 405 ou 404 et le message est perdu.
  3. Avec l'action pointant vers votre endpoint Ikiform, l'envoi part vers un serveur qui l'accepte.
  4. Ikiform vérifie le spam, stocke le message et vous l'envoie par e-mail.
  5. Ikiform répond par une redirection 303, et le navigateur charge la page de remerciement de votre propre site.
Seule l'action du formulaire change. La page, l'hébergeur et le déploiement restent tels quels.

Ce qui arrive à chaque message

Ikiform est l'endpoint vers lequel le formulaire envoie. Il n'héberge pas votre page et n'affiche pas votre formulaire ; vous gardez les deux. Pour chaque envoi, il lance les vérifications anti-spam, stocke le message et le diffuse aux endroits où vous le lisez.

Ce qu'Ikiform fait d'un message de formulaire de contactformulaire decontactsur tout hébergeurstatiqueIkiformanti-spam, puis stockagee-mail denotificationRépondre écrit àl'expéditeurtableau de bordrecherche, filtre spam,CSVwebhook signéSlack, Discord, votrecodeenvoiCe qu'Ikiform fait d'un message de formulaire de contactformulairede contactsur touthébergeurstatiqueIkiformanti-spam, puisstockagee-mail denotificationRépondre écrit àl'expéditeurtableau de bordrecherche, filtrespam, CSVwebhook signéSlack, Discord,votre codeenvoi
  1. Un visiteur remplit le formulaire de contact, qui l'envoie à votre endpoint Ikiform.
  2. Ikiform vérifie le honeypot, la limite de débit et le score de spam, puis stocke le message.
  3. Chaque destinataire des notifications reçoit un e-mail, avec l'adresse de l'expéditeur comme adresse de réponse.
  4. Le message apparaît dans le tableau de bord, où vous pouvez le rechercher, restaurer le spam et exporter en CSV.
  5. Si vous ajoutez un webhook, Ikiform envoie une copie signée vers Slack, Discord, un outil d'automatisation ou votre propre code.
L'e-mail suffit pour la plupart des formulaires de contact. Ajoutez un webhook quand les messages doivent arriver là où vous travaillez déjà.

Cinq façons de recevoir le formulaire d'un site statique

ApprochePoints fortsCompromis
Un lien mailto: ou action="mailto:..."Aucune configuration. Convient à une page personnelle avec peu de trafic.Ouvre l'appli de messagerie du visiteur, s'il en a configuré une. Rien n'est stocké ni filtré, et votre adresse est dans le HTML, à la portée des scrapers.
La fonctionnalité de formulaires de votre hébergeur, comme Netlify FormsUn seul attribut, pas d'autre compte, et généralement inclus dans l'offre d'hébergement.Ne fonctionne que chez cet hébergeur. Changer d'hébergeur oblige à refaire le formulaire, et les formulaires rendus en JavaScript demandent des contournements.
Une fonction serverless que vous écrivezContrôle total sur la validation, le stockage et l'e-mail envoyé.Vous gérez désormais un backend : une clé d'API d'e-mail, le filtrage du spam, les limites de débit, les nouvelles tentatives et les logs. Chaque hébergeur a son propre format de fonction.
Un formulaire hébergé intégré, comme Google Forms, Tally ou TypeformPas de code. Bien pour les formulaires longs ou en plusieurs étapes.Une iframe ou un lien vers un autre site, avec leur apparence et leurs scripts sur votre page.
Un backend de formulaires, comme Ikiform, Formspree, Basin ou Web3FormsVotre propre HTML sur n'importe quel hébergeur. Stockage, filtrage du spam et e-mail sans serveur.Un compte et une facture de plus, et les données du formulaire sont chez ce service.

Si vous êtes sur Netlify et n'avez aucune intention d'en partir, Netlify Forms est imbattable en termes d'effort. Si vous voulez écrire le backend, une fonction est un bon projet de week-end. Si vous voulez que le formulaire continue de fonctionner quand vous changez d'hébergeur ou de framework, utilisez un backend de formulaires. Les comparatifs mettent les backends côte à côte.

Le formulaire

Tous les exemples ci-dessous envoient depuis le navigateur, car un déploiement statique n'a pas de serveur depuis lequel envoyer. Choisissez votre stack ; ce choix s'applique à tous les exemples de code de ces guides. Les générateurs de sites statiques ont leurs propres pages de configuration : Jekyll, Hugo, Eleventy et Gatsby.

C'est toute l'intégration pour un site écrit à la main. Le formulaire envoie de façon classique, il fonctionne donc avec JavaScript désactivé, et le navigateur arrive sur votre thanks.html après un envoi réussi.

contact.html
<form id="contact" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <label for="name">Name</label>
  <input id="name" name="name" autocomplete="name" maxlength="120" required />
  <p class="field-error" data-error-for="name" hidden></p>

  <label for="email">Email</label>
  <input id="email" name="email" type="email" autocomplete="email" required />
  <p class="field-error" data-error-for="email" hidden></p>

  <label for="message">Message</label>
  <textarea id="message" name="message" rows="6" minlength="10" maxlength="5000" required></textarea>
  <p class="field-error" data-error-for="message" hidden></p>

  <input type="hidden" name="_redirect" value="https://example.com/thanks.html" />
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button type="submit">Send message</button>
  <p id="contact-status" role="status"></p>
</form>
<p id="contact-sent" role="status" hidden>Thanks. Your message is on its way, and I'll reply by email.</p>

<script src="/contact.js" defer></script>

Le script est facultatif. Il garde le visiteur sur la page, place chaque erreur de champ sous son input, et laisse le message saisi en place en cas d'échec.

contact.js
const form = document.querySelector("#contact");
const status = document.querySelector("#contact-status");
const done = document.querySelector("#contact-sent");
const button = form.querySelector("button");

function showErrors(fields = {}) {
  for (const slot of form.querySelectorAll("[data-error-for]")) {
    const name = slot.dataset.errorFor;
    const message = fields[name]?.[0] ?? "";
    slot.textContent = message;
    slot.hidden = !message;
    form.elements[name]?.setAttribute("aria-invalid", message ? "true" : "false");
  }
}

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  button.disabled = true;
  status.textContent = "Sending";
  try {
    const response = await fetch(form.action, {
      method: "POST",
      headers: { Accept: "application/json" },
      body: new FormData(form),
    });
    const result = await response.json();
    if (result.ok) {
      form.hidden = true;
      done.hidden = false;
      return;
    }
    showErrors(result.error.fields);
    status.textContent = result.error.message;
  } catch {
    status.textContent = "Couldn't send. Check your connection and try again.";
  }
  button.disabled = false;
});
  • Remplacez frm_xxxxxxxxxxxxxxxx par l'id de votre formulaire, visible dans le tableau de bord. L'id est public par conception : il est dans votre HTML dès le déploiement. Ne mettez jamais de clé d'API secrète dans un site statique.
  • Gardez email comme nom du champ e-mail. Ikiform en fait l'adresse de réponse des e-mails de notification et le compare aux domaines d'e-mails jetables.
  • L'input masqué _gotcha est le honeypot. Les humains ne le voient jamais ; les bots qui remplissent tous les inputs se signalent eux-mêmes comme spam.
  • Les versions JavaScript envoient Accept: application/json : Ikiform répond donc 201 en cas de succès, ou 422 avec un objet error.fields qui nomme chaque champ et le problème.

Si votre framework tourne bien sur un serveur en production, vous pouvez envoyer depuis celui-ci et garder l'endpoint hors de la page. Les guides Next.js, SvelteKit et Nuxt montrent les versions serveur, et Depuis un serveur couvre les clés secrètes.

Les versions côté serveur envoient avec une clé secrète, donc Ikiform ignore Allowed domains et sa vérification humaine. Chaque envoi vient alors de l'IP de votre serveur, et la limite Standard de 5 par minute et par visiteur compte tous vos visiteurs ensemble. Choisissez la limite de débit Relaxed ou Custom pour un formulaire que vous envoyez via un serveur, et gardez IKIFORM_SECRET_KEY dans des variables d'environnement réservées au serveur.

Les champs d'un formulaire de contact

Nom, e-mail et message couvrent presque tous les formulaires de contact. Un select de sujet vaut la peine quand des personnes différentes répondent à des types de messages différents. Essayez le formulaire ci-dessous : il exécute le validateur d'Ikiform dans votre navigateur et montre ce qui serait stocké, ou le 422 que vous recevriez.

Aperçu

Exécute le validateur d'Ikiform dans votre navigateur. Rien n'est envoyé.

La même définition que le modèle de formulaire de contact. Collez-la dans l'onglet Fields du formulaire et Ikiform l'applique côté serveur, quoi que dise le HTML.
ChampPourquoiÀ garder en tête
namePour saluer la personne dans la réponseUn lien dans un champ nom est un fort signal de spam : gardez donc l'id name
emailOù part la réponseObligatoire. type="email" affiche le bon clavier sur mobile
topicEnvoyer les ventes et le support à la bonne personneUn select n'accepte que les valeurs listées, on peut donc filtrer dessus en toute sécurité
messageLa raison d'être du formulaireUne longueur minimale de 10 empêche les envois d'un seul mot

Laissez de côté les numéros de téléphone, la taille de l'entreprise et tout ce que vous n'utiliserez pas dans votre première réponse. Chaque champ ajouté vous coûte des messages. Sans définition de champs, Ikiform stocke tous les inputs nommés que le formulaire envoie ; avec une définition, il rejette les envois non conformes.

L'envoi de fichiers n'est pas encore pris en charge. Un envoi avec un fichier reçoit 422 files_not_supported.

Répondre depuis votre boîte

Les e-mails de notification vont jusqu'à 10 destinataires confirmés par formulaire. Quand le formulaire a un champ email, l'adresse de réponse de l'e-mail est l'expéditeur : Reply dans votre appli de messagerie lui répond donc directement. Vous ne copiez jamais d'adresse depuis un tableau de bord.

Les notifications sont plafonnées à 50 e-mails par formulaire et par heure, puis un récapitulatif horaire, pour qu'une vague de spam ne puisse pas noyer votre boîte. Les messages marqués comme spam n'envoient aucun e-mail. _subject ne change pas l'objet des e-mails de notification. Il est transmis aux webhooks et à l'API sous meta.subject.

Ikiform n'envoie pas d'e-mail à la personne qui remplit le formulaire. Les e-mails de notification vont uniquement à vous et à votre équipe : il n'y a donc ni réponse automatique, ni e-mail de confirmation, ni double opt-in. Envoyez-les depuis votre outil d'e-mailing, déclenché par un webhook.

Vous voulez les messages dans un chat d'équipe au lieu de l'e-mail, ou en plus ? Ajoutez un webhook et suivez Slack, Discord ou Telegram.

Ce que voit le visiteur après l'envoi

Un envoi HTML classique se termine par une redirection. Ikiform répond à un envoi réussi depuis un navigateur par 303 See Other, et le navigateur charge la cible avec un GET, que votre hébergeur statique sert sans problème.

Les trois requêtes derrière l'envoi d'un formulaire HTML classiquenavigateurle visiteur cliquesur Envoyerikiform.com/f/…stocke le messagevotre/thanks.htmlun fichierstatiquePOST303, puis GETLes trois requêtes derrière l'envoi d'un formulaire HTML classiquenavigateurle visiteur clique sur Envoyerikiform.com/f/…stocke le messagevotre /thanks.htmlun fichier statiquePOST303, puis GET
  1. Le navigateur envoie les champs du formulaire à votre endpoint Ikiform.
  2. Ikiform stocke le message et répond 303 See Other avec un en-tête Location.
  3. Le navigateur suit la redirection avec un GET vers votre page de remerciement, que l'hébergeur statique sert comme n'importe quel fichier.
Sans cible de redirection, l'étape 3 charge la page de remerciement hébergée par Ikiform au lieu de la vôtre.

Ikiform choisit la cible dans cet ordre :

  • Redirect after submit dans les réglages du formulaire. N'importe quelle URL http: ou https: fonctionne, car seule votre équipe peut la définir. C'est le choix le plus simple pour un site unique.
  • Un champ _redirect dans le formulaire, s'il s'agit d'une URL https: sur l'un des Allowed domains du formulaire. Utilisez-le quand un formulaire sert plusieurs sites ou plusieurs langues.
  • Sinon, la page de remerciement hébergée par Ikiform, qui renvoie vers votre site quand l'envoi venait d'une page https: autorisée.

Faites de la page de remerciement un vrai fichier et excluez-la des résultats de recherche. Indiquez quand vous répondrez ; cela vous évite un second e-mail de la même personne.

thanks.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="robots" content="noindex" />
    <title>Message sent</title>
  </head>
  <body>
    <h1>Thanks, your message is on its way</h1>
    <p>I read every message and reply within two working days, from the address on this site.</p>
    <p><a href="/">Back to the home page</a></p>
  </body>
</html>

Les versions JavaScript ci-dessus se passent de redirection. Un envoi avec Accept: application/json reçoit du JSON en retour, et la page affiche elle-même la phrase de succès.

Le spam sur un formulaire statique

Votre endpoint est dans le code source de votre page, et les bots lisent le code source. Attendez-vous à du spam dans les jours qui suivent la mise en ligne. Ikiform superpose ses défenses pour que vous puissiez démarrer sans rien configurer :

DéfenseCe qu'elle arrêteConfiguration
HoneypotLes bots qui remplissent tous les inputsL'input masqué _gotcha
Score de spamDes liens dans le nom (+3), beaucoup de liens (+2), des formules de spam connues (+2) et des adresses jetables. À 5, un message est stocké comme spamAucune
Limites de débitLes scripts qui envoient en boucleActivée par défaut : Standard, 5 par minute et par visiteur
Domaines autorisésLes copies de votre formulaire qui envoient depuis d'autres sitesAjoutez vos domaines dans les réglages du formulaire
Vérification humaineLes bots qui pilotent un vrai navigateurRéglez Spam check sur Ikiform pour Cloudflare Turnstile sans clés

Le spam reçoit la même réponse de succès qu'un vrai message, pour que le bot n'apprenne rien, et n'envoie ni e-mail ni webhook. Un formulaire de contact a rarement besoin de plus d'un envoi par personne en quelques minutes : Strict, 2 toutes les 10 minutes par visiteur, lui convient une fois en ligne. La documentation anti-spam couvre chaque couche, et la vérification humaine demande un appel de plus dans les versions JavaScript : challenge() du SDK.

Domaines autorisés et déploiements de préproduction

Allowed domains correspond à un domaine et à ses sous-domaines. Les hébergeurs statiques vous donnent plus de domaines que vous ne le pensez : le domaine de production, le sous-domaine propre à l'hébergeur, et une adresse de préproduction par branche ou par pull request. Certaines préproductions sont des sous-domaines de l'adresse de votre site chez l'hébergeur et correspondent ; d'autres non, et demandent leur propre entrée.

HébergeurÀ ajouterPréproductions
GitHub Pagesusername.github.io et votre domaine personnaliséPas de préproduction par défaut
Vercelyour-project.vercel.app et votre domaineLes URL de branche ne correspondent pas ; ajoutez l'hôte exact pendant vos tests
Netlifyyour-site.netlify.app et votre domainedeploy-preview-12--your-site.netlify.app ne correspond pas
Cloudflare Pagesyour-project.pages.dev et votre domaineabc123.your-project.pages.dev correspond déjà

N'ajoutez jamais seul un hôte partagé, comme github.io, vercel.app ou netlify.app. Tous les sites de cet hébergeur correspondraient, y compris ceux des autres.

Si votre site envoie une Content-Security-Policy

Beaucoup de modèles statiques livrent une Content-Security-Policy stricte. Deux directives décident si le formulaire fonctionne : form-action pour l'envoi HTML classique, et connect-src pour les versions fetch. Oubliez l'une ou l'autre et le navigateur bloque la requête avant qu'elle ne quitte la page, avec pour seul indice un message dans la console.

En-tête de réponse
Content-Security-Policy: default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com

Gardez 'self' dans form-action : certains navigateurs vérifient aussi la redirection vers votre page de remerciement par rapport à cette directive. L'endroit où se trouve l'en-tête dépend de l'hébergeur : _headers sur Netlify et Cloudflare Pages, vercel.json sur Vercel, une balise <meta> sur GitHub Pages, et une politique d'en-têtes de réponse CloudFront devant S3. Les pages par hébergeur ci-dessous montrent chacune.

Tester avant de déployer

Ouvrez la page via un serveur local, pas en double-cliquant sur le fichier. Une page ouverte en file:// envoie Origin: null, qui ne correspond à aucun domaine autorisé et ne compte pas comme un test local. N'importe laquelle de ces commandes sert le dossier courant :

Terminal
npx serve .

python3 -m http.server 8000

Ajoutez localhost aux Allowed domains pendant vos tests. Les envois depuis localhost comptent comme des tests : un espace de travail sans offre en a 50 par mois, vous pouvez donc construire tout le formulaire avant de payer.

Choisissez votre cas

Le formulaire ci-dessus fonctionne sur tous les hébergeurs statiques. Chaque hébergeur a sa propre façon d'échouer, ses propres domaines de préproduction et ses fichiers d'en-têtes. Ces pages les couvrent.

Vérifiez que tout fonctionne

Faites ces vérifications une fois sur votre domaine en ligne et une fois sur une préproduction, si votre hébergeur en crée. Ikiform n'a pas d'offre gratuite. Les tests sont gratuits : un espace de travail sans offre accepte 50 soumissions de test par mois depuis localhost. Pour passer en production, choisissez Pro à partir de 6 $/mois pour 500 soumissions, ou payez une seule fois : 10 000 soumissions pour 49 $ ou 50 000 soumissions pour 149 $, avec des crédits qui n'expirent jamais. Voir les tarifs.

  1. Dans Allowed domains du formulaire, ajoutez le domaine de votre site, plus localhost pendant le développement. Laissez vide pour accepter les envois de partout.
  2. Envoyez depuis votre serveur local et vérifiez que le message apparaît sous Submissions.
  3. Déployez, puis envoyez depuis le site en ligne avec JavaScript désactivé. Vous devez arriver sur votre propre page de remerciement.
  4. Réactivez JavaScript et envoyez avec un message vide. L'erreur doit apparaître sur la page, pas sous forme de page d'erreur hébergée.
  5. Vérifiez la boîte de réception de chaque destinataire des notifications. Chacun reçoit un e-mail avec les champs, et Reply répond à l'expéditeur quand le formulaire a un champ email.
  6. Envoyez avec le honeypot rempli, _gotcha=bot. L'envoi reçoit quand même une réponse de succès, mais la soumission est stockée comme spam et n'envoie ni e-mail ni webhook.
  7. Rien n'est arrivé ? Passez le filtre de statut du tableau de bord sur Spam. Le spam reçoit la même réponse de succès qu'une vraie soumission.

Dépannage

ProblèmeQue faire
405 Method Not Allowed ou 404 juste après avoir cliqué sur EnvoyerLe formulaire a envoyé vers votre propre site. Vérifiez l'attribut action dans le HTML déployé, pas dans vos sources ; une étape de build ou un composant <Form> de framework l'a peut-être remplacé.
Rien ne se passe, et la console mentionne Content-Security-PolicyAjoutez https://ikiform.com à form-action pour les envois classiques et à connect-src pour fetch.
403 origin_not_allowed, ou tous les envois finissent en spamLe domaine de la page n'est pas dans les Allowed domains du formulaire. Ajoutez-le, plus localhost pour les tests en local. Les sous-domaines d'un domaine listé sont autorisés.
Les tests en local sont refusés avec 402 plan_requiredLa page a été ouverte via une URL file:// : l'envoi n'avait donc pas d'origine exploitable et a compté comme un envoi réel. Servez le dossier avec un serveur local et testez depuis localhost.
Le navigateur arrive sur la page de remerciement d'Ikiform au lieu de la vôtre_redirect est ignoré sauf si son domaine figure dans Allowed domains et qu'il s'agit d'une URL https:. Ajoutez le domaine, ou définissez plutôt Redirect after submit dans les réglages du formulaire.
422 validation_failedL'envoi ne correspond pas à la définition des champs du formulaire. error.fields nomme chaque champ et la raison. Faites correspondre le name de l'input à l'id du champ.
La soumission est dans le tableau de bord mais aucun e-mail ni webhook n'est arrivéElle a été stockée comme spam. Le spam n'envoie rien. Ouvrez-la sous le filtre Spam pour voir les raisons, et marquez-la comme non-spam pour envoyer son e-mail et ses webhooks.
429 rate_limitedUn visiteur a envoyé plus que ce que permet la limite de débit du formulaire. Standard autorise 5 envois par minute et par visiteur. Attendez la durée de Retry-After, ou choisissez Relaxed ou Custom pour les formulaires qui envoient depuis une seule IP partagée.

Questions

Une étape vous bloque, ou vous voulez un guide pour votre cas ? Écrivez à hello@ikiform.com.