# 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.

Publié le 2026-10-25 · https://ikiform.com/fr/guides/newsletter-signup/static-site

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.

Schéma : Où se trouve la clé d'API de l'outil d'e-mailing sur un site statique

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.

**Hugo** (`layouts/partials/newsletter.html`)

```html
<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>
```

**Jekyll** (`_includes/newsletter.html`)

```html
<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="{{ include.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>
```

**Eleventy** (`_includes/newsletter.njk`)

```html
<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 or "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>
```

**Astro** (`src/components/Newsletter.astro`)

```astro
---
interface Props {
  source?: string;
}
const { source = "footer" } = Astro.props;
---

<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} />
  <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 :

**Hugo** (`layouts/_default/single.html`)

```html
{{ partial "newsletter.html" (dict "source" "post") }}
```

**Jekyll** (`_layouts/post.html`)

```html
{% include newsletter.html source="post" %}
```

**Eleventy** (`_includes/post.njk`)

```html
{% set source = "post" %}
{% include "newsletter.njk" %}
```

- 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`:

```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 statique | Options pour le relais |
| --- | --- |
| GitHub Pages, S3 | Pas de fonctions. Utilisez un Cloudflare Worker sur un domaine personnalisé, ou un outil d'automatisation |
| Netlify | Une Netlify Function sur le domaine de votre site, ou l'une des options ci-dessus |
| Cloudflare Pages | Un Worker séparé sur un domaine personnalisé. Ikiform refuse les URL `pages.dev` et `workers.dev` |
| Vercel | Une 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](https://ikiform.com/fr/guides/newsletter-signup/kit.md), [MailerLite](https://ikiform.com/fr/guides/newsletter-signup/mailerlite.md) et [beehiiv](https://ikiform.com/fr/guides/newsletter-signup/beehiiv.md). Sans aucun code, faites pointer le webhook vers [Zapier](https://ikiform.com/blog/form-submissions-to-zapier.md) ou [n8n](https://ikiform.com/blog/form-submissions-to-n8n.md) et utilisez leur action pour l'outil d'e-mailing. La [documentation des webhooks](https://ikiform.com/fr/docs/webhooks.md) 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`:

```text
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ème | Que faire |
| --- | --- |
| Rien dans le tableau de bord, et une erreur CSP dans la console | Ajoutez `https://ikiform.com` à `form-action` et `connect-src` dans votre Content-Security-Policy. |
| `422` avec `unknown_field` pour `source` | Ajoutez `source` à la définition des champs du formulaire, ou supprimez l'input masqué. |
| `403 origin_not_allowed`, ou tous les envois finissent en spam | Le 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

### Puis-je ajouter un formulaire d'inscription à la newsletter à un site statique sans backend ?

Oui. Le formulaire envoie depuis le navigateur à un backend de formulaires comme Ikiform, qui stocke et filtre chaque inscription. Un relais ou un outil d'automatisation hébergé ailleurs la transmet à votre outil d'e-mailing : le site statique lui-même n'exécute aucun code serveur.

### Puis-je appeler l'API de mon outil d'e-mailing directement depuis la page statique ?

Non. Toute clé dans le JavaScript de la page est visible par chaque visiteur, et les clés des outils d'e-mailing peuvent généralement lire ou modifier toute la liste. Gardez la clé dans un relais qui tourne hors du site.

### Est-ce que cela fonctionne sur GitHub Pages ?

Oui. Le formulaire est du HTML simple et envoie à Ikiform. GitHub Pages ne peut pas exécuter le relais : hébergez-le sur un Cloudflare Worker avec un domaine personnalisé, ou utilisez un outil d'automatisation comme Zapier ou n8n.

### Qui envoie l'e-mail de confirmation ?

Votre outil d'e-mailing. Ikiform n'écrit jamais à la personne qui s'inscrit. Le relais ajoute le lecteur comme non confirmé et l'outil envoie son propre lien de confirmation.

## Autres guides

- [Formulaire d'inscription à la newsletter](https://ikiform.com/fr/guides/newsletter-signup.md): Créez un formulaire d'inscription à la newsletter avec votre propre markup, enregistrez le consentement, bloquez les inscriptions de bots et transmettez chaque abonné à Kit, MailerLite, beehiiv ou n'importe quel outil.
- [Double opt-in](https://ikiform.com/fr/guides/newsletter-signup/double-opt-in.md): Ikiform n'envoie pas d'e-mails de confirmation : le double opt-in se fait donc dans votre outil d'e-mailing. Un relais ajoute chaque inscription comme non confirmée et l'outil envoie le lien.
- [Kit (ConvertKit)](https://ikiform.com/fr/guides/newsletter-signup/kit.md): Reliez votre propre formulaire de newsletter à Kit avec un relais qui crée l'abonné, l'ajoute à un formulaire Kit pour l'incentive email et étiquette l'emplacement.
- [beehiiv](https://ikiform.com/fr/guides/newsletter-signup/beehiiv.md): Reliez votre propre formulaire d'inscription à beehiiv : un relais crée chaque abonnement avec le double opt-in activé, l'emplacement comme source UTM et la page comme referrer.
- [MailerLite](https://ikiform.com/fr/guides/newsletter-signup/mailerlite.md): Reliez votre propre formulaire d'inscription à MailerLite : un relais ajoute les nouveaux lecteurs comme non confirmés pour le double opt-in, sans jamais demander deux fois à un abonné de confirmer.
- [Formulaire de newsletter Next.js](https://ikiform.com/fr/guides/newsletter-signup/nextjs.md): Un formulaire de newsletter en pied de page Next.js : une server action avec vérification du consentement et Turnstile, qui envoie à Ikiform, plus un route handler qui ajoute les lecteurs à Kit.
