Formulaire d'inscription à la newsletter

Formulaire d'inscription à la newsletter6 min de lecture

Un formulaire d'inscription par e-mail sur un site statique, sans serveur

Ajoutez un formulaire d'inscription à la newsletter à un site statique Hugo, Jekyll, Eleventy ou Astro, gardez la clé d'API de l'outil d'e-mailing hors de la page et confirmez dans l'outil.

Preet SutharFondateur, Ikiform

Sur un site statique, le formulaire envoie directement à Ikiform depuis le navigateur du lecteur, puisqu'il n'y a aucun serveur à vous vers lequel envoyer. Gardez la clé d'API de votre outil d'e-mailing totalement hors du site : le navigateur ne connaît que votre endpoint Ikiform public, et un relais hébergé ailleurs ajoute l'abonné. Écrivez le formulaire une fois sous forme de partial ou d'include, passez une source différente par emplacement, et ajoutez un petit script pour la version sans rechargement.

Ce qui change ici

Un site statique, ce sont des fichiers chez un hébergeur : GitHub Pages, Netlify, Cloudflare Pages, un bucket S3. Rien de ce que vous écrivez ne tourne sur un serveur quand un lecteur envoie un formulaire. Cela exclut le raccourci habituel qui consiste à appeler l'API de votre outil d'e-mailing depuis la page, car tout ce qu'exécute le navigateur, n'importe qui peut le lire.

Les clés d'API des outils d'e-mailing se limitent rarement à l'ajout d'un abonné. Une clé qui peut ajouter un abonné peut généralement aussi lire, exporter ou supprimer toute la liste. Mettez-la dans le JavaScript d'un site statique et elle part chez chaque visiteur, et de là chez quiconque ouvre les outils de développement.

Où se trouve la clé d'API de l'outil d'e-mailing sur un site statiqueclé dans la pageclé dans un relaispagestatiqueclé d'APIdans sonJavaScriptAPI del'outild'e-mailingn'importe quipeut l'appelervotrelistelisible partouspagestatiqueendpointpublicseulementIkiformanti-spam,webhook signérelaisdétient laclé d'APIoutild'e-mailingajoutel'abonnécléenvoisignécléOù se trouve la clé d'API de l'outil d'e-mailing sur un site statiqueclé dans la pageclé dans un relaispage statiqueclé d'API dans son JavaScriptAPI de l'outild'e-mailingn'importe qui peut l'appelervotre listelisible par touspage statiqueendpoint public seulementIkiformanti-spam, webhook signérelaisdétient la clé d'APIoutil d'e-mailingajoute l'abonnécléenvoisignéclé
  1. Avec la clé dans la page, chaque visiteur la télécharge, et n'importe qui peut appeler l'API de l'outil d'e-mailing avec.
  2. Avec un relais, la page ne connaît que votre endpoint Ikiform, qui accepte les envois de formulaire et rien d'autre.
  3. Ikiform filtre le spam et envoie un webhook signé au relais.
  4. Le relais, hébergé hors du site statique, détient la clé d'API et ajoute l'abonné.
L'endpoint du formulaire peut être publié sans risque. La clé d'API, jamais.

Écrire le formulaire une fois

Un formulaire de newsletter apparaît dans plusieurs templates : le pied de page, la fin de chaque article, une page newsletter. Faites-en un partial qui accepte une valeur source, pour que chaque emplacement s'enregistre lui-même. Le markup est identique dans chaque générateur ; seule la syntaxe de template pour source change.

<form data-newsletter action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <input name="email" type="email" required autocomplete="email" placeholder="you@example.com" aria-label="Email" />
  <label><input name="consent" type="checkbox" value="true" required /> Email me the newsletter. I can unsubscribe at any time.</label>
  <input type="hidden" name="source" value="{{ .source | default "footer" }}" />
  <input type="hidden" name="_redirect" value="https://example.com/subscribed/" />
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button>Subscribe</button>
  <p role="status" hidden></p>
</form>

Insérez-le ensuite dans un template avec le nom de l'emplacement :

{{ partial "newsletter.html" (dict "source" "post") }}
  • Dans Astro, utilisez <Newsletter source="post" />. Un build Astro statique ne peut pas exécuter d'Actions : le composant envoie donc directement à Ikiform comme les autres.
  • Ajoutez source à la définition des champs du formulaire dans Ikiform, en texte avec une longueur maximale courte. Une fois les champs définis, un input que la définition ne liste pas est refusé avec unknown_field.
  • _redirect doit être une URL https: sur l'un des Allowed domains du formulaire. Créez la page /subscribed/ avec une ligne qui invite les lecteurs à vérifier leur boîte de réception.

Un seul script pour tous les formulaires de la page

Une page d'article peut avoir deux formulaires de newsletter, un après l'article et un dans le pied de page. Ce script gère chaque formulaire marqué data-newsletter, et quand l'un réussit, il les remplace tous, pour qu'on ne redemande à personne de s'abonner quelques centimètres plus bas. Chargez-le une fois depuis votre layout de base.

static/newsletter.js
for (const form of document.querySelectorAll("form[data-newsletter]")) {
  const status = form.querySelector('[role="status"]');
  const button = form.querySelector("button");

  form.addEventListener("submit", async (event) => {
    event.preventDefault();
    button.disabled = true;
    status.hidden = false;
    status.textContent = "Subscribing";
    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) {
        for (const other of document.querySelectorAll("form[data-newsletter]")) {
          other.replaceChildren(Object.assign(document.createElement("p"), {
            textContent: "Almost done. Check your inbox for a confirmation link.",
          }));
        }
        return;
      }
      if (response.status === 429 && response.headers.has("Retry-After")) {
        status.textContent = "Too many tries from this network. Wait a minute and try again.";
      } else {
        const errors = result.error.fields ?? {};
        status.textContent = errors.email?.[0] ?? errors.consent?.[0] ?? result.error.message;
      }
    } catch {
      form.submit();
      return;
    }
    button.disabled = false;
  });
}
  • Si fetch échoue, par exemple derrière un filtre réseau strict, le script se rabat sur form.submit(), qui envoie de façon classique et arrive sur /subscribed/.
  • Un 429 a son propre message. Les lecteurs d'un même réseau de bureau ou de campus partagent une IP et peuvent atteindre ensemble la limite par visiteur.
  • Le formulaire fonctionne avec JavaScript désactivé. Le script ne change que la façon d'afficher la réponse.

Les lecteurs sans JavaScript arrivent sur /subscribed/ : faites donc faire à cette page ce que fait le script pour tous les autres. Dites qu'un e-mail de confirmation arrive, nommez l'expéditeur à chercher, mentionnez le dossier spam et mettez un lien vers le blog. C'est une page ordinaire de votre générateur, construite comme les autres, et c'est aussi là que vous pouvez faire pointer ensuite le lien de confirmation de l'outil d'e-mailing.

Où vit le relais

Le relais a besoin d'une URL HTTPS publique qui exécute du code. Votre hébergeur statique en propose peut-être une, ou pas :

Hébergeur statiqueOptions pour le relais
GitHub Pages, S3Pas de fonctions. Utilisez un Cloudflare Worker sur un domaine personnalisé, ou un outil d'automatisation
NetlifyUne Netlify Function sur le domaine de votre site, ou l'une des options ci-dessus
Cloudflare PagesUn Worker séparé sur un domaine personnalisé. Ikiform refuse les URL pages.dev et workers.dev
VercelUne fonction sur votre domaine. La sortie statique peut cohabiter avec une route API

Le code du relais pour chaque outil d'e-mailing est sur les pages Kit, MailerLite et beehiiv. Sans aucun code, faites pointer le webhook vers Zapier ou n8n et utilisez leur action pour l'outil d'e-mailing. La documentation des webhooks liste les règles d'URL.

Les sites statiques ont souvent des aperçus de déploiement sur un domaine séparé. Les envois depuis un domaine absent des Allowed domains sont marqués comme spam ou refusés : ajoutez donc le domaine d'aperçu pendant vos tests, ou testez sur le domaine de production et sur localhost.

Si votre site envoie une Content Security Policy

Beaucoup d'hébergeurs statiques permettent de définir des en-têtes depuis un fichier, comme _headers sur Netlify et Cloudflare Pages. Si le vôtre envoie une Content-Security-Policy, le navigateur bloque le formulaire sauf si la politique autorise Ikiform. form-action couvre l'envoi classique et connect-src le fetch du script :

_headers
Content-Security-Policy: default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com

Un envoi bloqué apparaît comme une erreur CSP dans la console du navigateur et n'atteint jamais Ikiform : rien n'apparaît donc non plus dans le tableau de bord.

Vérifiez que tout fonctionne

  1. Ajoutez votre domaine de production et localhost sous Allowed domains.
  2. Buildez le site et ouvrez une page d'article. Les deux formulaires sont là, chacun avec sa propre valeur source dans le code source de la page.
  3. Abonnez-vous depuis le formulaire de l'article avec une adresse à vous. Les deux formulaires affichent la ligne vous invitant à vérifier votre boîte.
  4. Désactivez JavaScript et abonnez-vous depuis le pied de page. Vous arrivez sur /subscribed/.
  5. Ouvrez Submissions dans le tableau de bord. Les deux inscriptions affichent post et footer comme source.
  6. Cherchez la clé d'API de votre outil d'e-mailing dans le site buildé. Elle n'y est pas.

Pièges

ProblèmeQue faire
Rien dans le tableau de bord, et une erreur CSP dans la consoleAjoutez https://ikiform.com à form-action et connect-src dans votre Content-Security-Policy.
422 avec unknown_field pour sourceAjoutez source à la définition des champs du formulaire, ou supprimez l'input masqué.
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.
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.

Questions

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