Formulaire de contact sur un site statique

Formulaire de contact sur un site statique7 min de lecture

Ajouter un formulaire de contact sur Netlify sans Netlify Forms

Un formulaire de contact sur Netlify sans Netlify Forms : pas de détection au build, envois JSON depuis React, et un formulaire à part pour les deploy previews.

Preet SutharFondateur, Ikiform

Laissez data-netlify hors du formulaire et faites pointer son action vers votre endpoint Ikiform ; Netlify sert alors la page sans jamais toucher à l'envoi. Les formulaires React et Vue n'ont besoin d'aucune copie HTML masquée, et peuvent envoyer du JSON. Définissez un id de formulaire différent pour les deploy previews dans netlify.toml, car les hôtes de préproduction ne correspondent pas à votre site dans Allowed domains.

Ce qui change sur Netlify

Netlify est le seul grand hébergeur statique avec une fonctionnalité de formulaires intégrée. Netlify Forms lit votre HTML déployé après chaque build, trouve les formulaires marqués data-netlify ou netlify, et accepte les envois vers votre propre site pour ces formulaires. Tout autre envoi vers une page statique échoue, et sur Netlify il échoue avec 404, pas 405 :

Terminal
curl -i -X POST -d "message=hello" https://your-site.netlify.app/contact/
Réponse
HTTP/2 404
server: Netlify
cache-status: "Netlify Edge"; fwd=method; fwd-status=404
content-length: 0

Ce 404 pousse à chercher une page manquante qui ne l'est pas. Il signifie qu'aucun formulaire détecté ne correspondait à l'envoi. Savoir s'il faut utiliser Netlify Forms est une autre question, et le comparatif Ikiform vs Netlify Forms y répond honnêtement : si votre site restera toujours sur Netlify et que le formulaire est en HTML simple, Netlify Forms demande moins de travail. Cette page couvre les autres cas.

Votre situationPourquoi Netlify Forms gêne
Le formulaire est rendu par React, Vue ou SvelteLe build analyse le HTML statique : un formulaire rendu côté client a donc besoin d'un double masqué que Netlify puisse trouver
Vous voulez envoyer du JSON depuis fetchNetlify Forms n'accepte que des corps urlencoded et multipart
Le site pourrait changer d'hébergeurLe formulaire envoie vers votre propre domaine : il ne fonctionne donc que tant que Netlify sert ce domaine
Les messages doivent arriver à un webhook que vous vérifiezIkiform signe chaque livraison avec les en-têtes Standard Webhooks et la réessaie
Netlify Forms et Ikiform côte à côte sur le même site Netlifyavec Netlify Formsavec IkiformbuildNetlify analyse leHTMLdata-netlify ?POST vers votresiteNetlify le stocke404 sur le POSTformulaire ignorébuildpas d'analysePOST versikiform.comHTML ou JSONIkiformstocke, e-mails,webhookstrouvénon trouvéNetlify Forms et Ikiform côte à côte sur le même site Netlifyavec Netlify Formsavec IkiformbuildNetlify analyse leHTMLdata-netlify ?POST versvotre siteNetlify le stocke404 surle POSTformulaireignorébuildpas d'analysePOST versikiform.comHTML ou JSONIkiformstocke, e-mails,webhookstrouvénon trouvé
  1. Avec Netlify Forms, Netlify analyse le HTML de chaque déploiement à la recherche des formulaires marqués data-netlify.
  2. Un formulaire trouvé accepte les envois vers votre propre site, et Netlify les stocke.
  3. Un formulaire non trouvé, comme un formulaire rendu en JavaScript sans copie masquée, reçoit 404 à chaque envoi.
  4. Avec Ikiform, rien n'a besoin d'être trouvé au build.
  5. Le formulaire envoie directement à ikiform.com, en HTML ou en JSON, et Ikiform le stocke et vous l'envoie par e-mail.
Les deux peuvent cohabiter sur un même site. Un formulaire avec data-netlify va chez Netlify ; un formulaire dont l'action est un endpoint Ikiform va chez Ikiform.

Sortir un formulaire de Netlify Forms

  • Retirez data-netlify ou netlify, netlify-honeypot et data-netlify-recaptcha du formulaire, ainsi que tout input masqué form-name. Si vous le laissez, form-name est stocké comme un champ ordinaire.
  • Supprimez la copie statique masquée d'un formulaire JavaScript, comme celle ci-dessous. Netlify n'en avait besoin que pour détecter le formulaire.
  • Réglez l'action, ou l'URL du fetch, sur votre endpoint Ikiform. L'ancien action="/thanks" passe dans le réglage Redirect after submit du formulaire.
  • Téléchargez d'abord les soumissions passées depuis la page Forms de Netlify. Elles ne migrent pas toutes seules.
index.html, à supprimer
<form name="contact" netlify hidden>
  <input name="name" />
  <input name="email" />
  <textarea name="message"></textarea>
</form>

Laissez la détection des formulaires activée si d'autres formulaires du site utilisent encore Netlify Forms. La documentation de Netlify indique que la désactivation n'est prévue que pour les sites qui n'utilisent pas Netlify Forms. Une fois que plus rien ne l'utilise, la désactiver évite une analyse à chaque déploiement.

Ce qui remplace les réglages anti-spam de Netlify

Netlify passe les soumissions dans Akismet et propose netlify-honeypot et reCAPTCHA. Sur Ikiform, le honeypot continue de fonctionner si vous réglez le Honeypot field du formulaire sur le nom que vous utilisez déjà, comme bot-field. Le score de spam s'applique à chaque envoi sans configuration, et Spam check réglé sur Ikiform remplace reCAPTCHA par Cloudflare Turnstile, sans clés. Retirez la div reCAPTCHA lors de la bascule.

Un formulaire React qui envoie du JSON

Sans Netlify Forms dans les pattes, un formulaire rendu côté client n'a pas besoin de double, et il peut envoyer du JSON, ce qui préserve les types et permet de nettoyer les valeurs au préalable. Celui-ci enregistre aussi quel contexte Netlify a construit la page :

src/ContactForm.tsx
import { type FormEvent, useState } from "react";

const ENDPOINT = `https://ikiform.com/f/${import.meta.env.VITE_IKIFORM_FORM_ID}`;

interface Message {
  name: string;
  email: string;
  message: string;
  context: string;
}

export function ContactForm() {
  const [sent, setSent] = useState(false);
  const [error, setError] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    const body: Message & Record<string, string> = {
      name: String(data.get("name") ?? "").trim(),
      email: String(data.get("email") ?? "").trim(),
      message: String(data.get("message") ?? "").trim(),
      context: import.meta.env.VITE_CONTEXT ?? "local",
      _gotcha: String(data.get("_gotcha") ?? ""),
    };
    setError("");
    const response = await fetch(ENDPOINT, {
      method: "POST",
      headers: { "Content-Type": "application/json", Accept: "application/json" },
      body: JSON.stringify(body),
    }).catch(() => null);
    const result = await response?.json().catch(() => null);
    if (result?.ok) return setSent(true);
    const fields: Record<string, string[]> = result?.error?.fields ?? {};
    setError(Object.values(fields)[0]?.[0] ?? result?.error?.message ?? "Couldn't send. Try again.");
  }

  if (sent) return <p role="status">Got it. I'll answer from my own inbox, usually the same day.</p>;

  return (
    <form onSubmit={onSubmit}>
      <label htmlFor="name">Name</label>
      <input id="name" name="name" autoComplete="name" required />
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" autoComplete="email" required />
      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" rows={6} minLength={10} required />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {error ? <p role="alert">{error}</p> : null}
      <button>Send</button>
    </form>
  );
}
  • Content-Type: application/json indique à Ikiform d'analyser du JSON ; Accept: application/json demande une réponse en JSON.
  • VITE_CONTEXT est un nom d'exemple. Netlify définit CONTEXT pendant le build ; exposez-le à Vite dans votre commande de build, ou supprimez le champ.
  • Le honeypot est envoyé comme clé JSON. Gardez-le dans le corps même si l'input est masqué, pour que les bots qui le remplissent se signalent toujours.

Les deploy previews ont leur propre formulaire

Les deploy previews vivent sur des hôtes comme deploy-preview-12--your-site.netlify.app. Ce n'est pas un sous-domaine de your-site.netlify.app, il ne correspond donc pas dans Allowed domains, et les envois des préproductions finissent en spam. Donnez aux previews et aux branch deploys leur propre formulaire dans netlify.toml, et définissez l'en-tête Content-Security-Policy dans le même fichier :

netlify.toml
[build]
  command = "npm run build"
  publish = "dist"

[context.production.environment]
  VITE_IKIFORM_FORM_ID = "frm_xxxxxxxxxxxxxxxx"

[context.deploy-preview.environment]
  VITE_IKIFORM_FORM_ID = "frm_yyyyyyyyyyyyyyyy"

[context.branch-deploy.environment]
  VITE_IKIFORM_FORM_ID = "frm_yyyyyyyyyyyyyyyy"

[[headers]]
  for = "/*"
  [headers.values]
    Content-Security-Policy = "default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com"

Laissez vides les Allowed domains du formulaire de préproduction, ou ajoutez l'hôte de préproduction exact pendant vos tests. Le formulaire de production reçoit votre domaine et your-site.netlify.app. Les en-têtes de netlify.toml s'appliquent à tous les contextes de déploiement ; si vous préférez un fichier _headers, il doit se retrouver dans le répertoire de publication.

N'ajoutez jamais netlify.app seul dans Allowed domains. Tous les sites Netlify correspondraient, y compris des copies de votre formulaire sur les sites d'autres personnes.

Vérifiez que tout fonctionne

  1. Ouvrez une deploy preview, envoyez, et vérifiez que le message arrive dans le formulaire de préproduction.
  2. Fusionnez, puis envoyez en production. Vérifiez que le message arrive dans le formulaire de production et que la page Forms de Netlify n'affiche rien de nouveau.
  3. 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.
  4. 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.
  5. 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.

Pièges

ProblèmeQue faire
404 à l'envoiL'envoi est parti vers votre propre site. Un build en cache a peut-être encore l'ancienne action, ou un appel fetch envoie toujours vers /. Cherchez form-name dans le répertoire de publication.
Les soumissions apparaissent toujours dans NetlifyUn formulaire de la page envoie encore vers votre propre site avec data-netlify ou netlify : Netlify continue donc de le détecter et de stocker ses envois. Retirez l'attribut, réglez l'action sur Ikiform et redéployez.
404 not_found d'Ikiform sur les préproductions uniquementLe contexte deploy preview n'a pas de VITE_IKIFORM_FORM_ID, l'URL se termine donc par undefined. Ajoutez-le sous [context.deploy-preview.environment].
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.
422 files_not_supportedLe formulaire a un <input type="file"> avec un fichier choisi. L'envoi de fichiers n'est pas encore pris en charge ; retirez l'input ou demandez plutôt un lien.

Questions

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