Liste d'attente pour un nouveau SaaS7 min de lecture
Comment ajouter une liste d'attente à un site Framer
Deux façons de collecter des inscriptions sur Framer : le formulaire Framer avec un webhook vers Ikiform, ou un code component qui garde limites et anti-spam.
Les formulaires Framer ne peuvent pas envoyer vers une URL externe, mais ils peuvent envoyer chaque soumission à un webhook : la voie sans code est donc le formulaire Framer avec votre endpoint Ikiform comme webhook. Pour un lancement public, utilisez plutôt un petit code component : le navigateur envoie directement, donc la limite de débit par visiteur, les domaines autorisés et le honeypot d'Ikiform continuent tous de fonctionner.
Framer est l'outil de nombreuses pages d'avant-lancement, car la page elle-même se fait en un après-midi. C'est le formulaire de liste d'attente qui est moins évident. Le bloc formulaire de Framer ne peut pas changer son action : vous choisissez donc entre deux voies qui se comportent différemment une fois le vrai trafic arrivé.
Ce qui change sur Framer
- Pas d'action personnalisée. Un formulaire Framer envoie vers les destinations de Framer : son propre stockage, l'e-mail, ou un Webhook. Un webhook est le seul moyen d'atteindre un backend externe sans code.
- Les webhooks viennent des serveurs de Framer, pas du visiteur. Ikiform voit les adresses IP de Framer et aucun en-tête
Origin. Cela désactive trois protections : les domaines autorisés, la limite de débit par visiteur et la vérification humaine. - Nouvelles tentatives. Framer traite tout ce qui n'est pas un
2xxcomme un échec et réessaie jusqu'à cinq fois. Il ne suit pas les redirections. Ikiform répond à un envoi JSON par201: une inscription stockée est donc un succès dès la première fois. - Les code components sont du vrai React. Ils s'exécutent dans le navigateur du visiteur sur le site publié : un formulaire qu'ils contiennent envoie donc comme n'importe quelle page web.
- Avec la voie webhook, le visiteur envoie le formulaire Framer et les serveurs de Framer transmettent les champs à Ikiform en JSON.
- Ikiform voit les serveurs de Framer comme expéditeur : il ne peut donc ni distinguer les visiteurs ni vérifier sur quel site se trouve le formulaire.
- Avec le code component, le navigateur du visiteur envoie directement à Ikiform.
- Ikiform voit l'IP de chaque visiteur et votre domaine : toutes les protections s'appliquent.
Choisir une voie
| Item | Formulaire Framer avec un webhook | Code component |
|---|---|---|
| Configuration | Deux champs dans la barre latérale droite | Un fichier de code, collé une fois |
| Style | Les styles et états du formulaire Framer | Des styles en ligne que vous modifiez, ou vos propres classes |
| Limite de débit par visiteur | Non. Tous les envois partagent les IP de Framer | Oui, 5 par minute chacun |
| Domaines autorisés | Laisser vide | Réglés sur vos domaines Framer et personnalisé |
| Honeypot | Non | Oui |
| Vérification humaine | Désactivée | Désactivée, car il envoie avec fetch |
| Suivi de la source | Aucune | ?ref= dans l'URL, ou une valeur par défaut par instance |
La voie webhook convient à une page que vous partagez avec quelques centaines de personnes. Si vous êtes sur le point de publier la page quelque part de très visible, utilisez le code component : une vague d'inscriptions de bots via les serveurs de Framer ne peut être arrêtée que par le score de spam.
Voie 1 : le formulaire Framer avec un webhook
- Ajoutez à la page un formulaire avec un input Email. Dans les réglages de l'input, nommez-le
email. Framer envoie le nom de chaque input comme clé JSON, et Ikiform utiliseemailpour l'adresse de réponse et la vérification des domaines jetables. - Sélectionnez le formulaire. À côté de Send To, choisissez Add… › Webhook et collez votre endpoint,
https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx. Le secret de signature est facultatif ; Ikiform ne vérifie pas la signature de Framer. - Dans Ikiform, laissez Allowed domains vide et la vérification anti-spam désactivée. Avec l'un ou l'autre activé, chaque envoi Framer est signalé ou refusé, car il n'a pas d'origine et ne peut pas exécuter de vérification dans un navigateur.
- Réglez la limite de débit sur Relaxed, ou Custom le jour du lancement. Standard compte tous les envois de Framer comme un seul visiteur, et le formulaire entier est plafonné à 300 par minute.
- Stylez l'état de succès du formulaire dans Framer. Il s'affiche quand Ikiform répond
201.
Voie 2 : un code component
Dans le panneau Assets, ouvrez Code, créez un fichier de code nommé WaitlistSignup et collez ceci. Faites-le glisser sur la page et définissez Form ID dans le panneau des propriétés.
import { addPropertyControls, ControlType } from "framer";
import { type CSSProperties, type FormEvent, useRef, useState } from "react";
const box: CSSProperties = {
font: "inherit",
padding: "12px 14px",
borderRadius: 10,
border: "1px solid rgba(0, 0, 0, 0.15)",
};
interface Props {
formId: string;
buttonLabel: string;
joinedText: string;
source: string;
}
export default function WaitlistSignup({ formId, buttonLabel, joinedText, source }: Props) {
const [status, setStatus] = useState<"idle" | "sending" | "joined">("idle");
const [error, setError] = useState("");
const key = useRef("");
async function onSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const body = new FormData(event.currentTarget);
const ref = new URLSearchParams(window.location.search).get("ref");
body.set("ref", (ref || source).slice(0, 40));
key.current ||= crypto.randomUUID();
setStatus("sending");
setError("");
try {
const response = await fetch(`https://ikiform.com/f/${formId}`, {
method: "POST",
headers: { Accept: "application/json", "Idempotency-Key": key.current },
body,
});
const result = await response.json();
if (result.ok) return setStatus("joined");
setError(result.error.fields?.email?.[0] ?? result.error.message);
} catch {
setError("Couldn't reach the server. Try again.");
}
setStatus("idle");
}
if (status === "joined") return <p role="status" style={{ margin: 0 }}>{joinedText}</p>;
return (
<form onSubmit={onSubmit} style={{ display: "flex", flexWrap: "wrap", gap: 8, width: "100%" }}>
<input
name="email"
type="email"
placeholder="you@company.com"
aria-label="Email"
autoComplete="email"
required
style={{ ...box, flex: "1 1 220px" }}
/>
<input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
<button type="submit" disabled={status === "sending"} style={{ ...box, cursor: "pointer" }}>
{status === "sending" ? "Joining" : buttonLabel}
</button>
{error ? <p role="alert" style={{ flexBasis: "100%", margin: 0 }}>{error}</p> : null}
</form>
);
}
addPropertyControls(WaitlistSignup, {
formId: { type: ControlType.String, title: "Form ID", defaultValue: "frm_xxxxxxxxxxxxxxxx" },
buttonLabel: { type: ControlType.String, title: "Button", defaultValue: "Join the waitlist" },
joinedText: { type: ControlType.String, title: "Joined text", defaultValue: "You're on the list." },
source: { type: ControlType.String, title: "Source", defaultValue: "framer" },
});- Le panneau des propriétés expose le libellé du bouton, le texte de confirmation et une Source par défaut. Placez une instance dans le hero avec
heroet une autre dans le pied de page avecfooter, et vous verrez laquelle convertit. - Un
?ref=dans l'URL de la page l'emporte sur la valeur par défaut : les liens de campagne se taguent donc tout seuls. - La clé d'idempotence est créée au premier envoi et réutilisée lors des nouvelles tentatives de la même visite : un double appui sur un téléphone ne stocke donc qu'une inscription.
windown'est lu que dans le gestionnaire d'envoi. Framer rend d'abord les composants côté serveur, et toucher àwindowpendant le rendu casserait cela.
Réglages Ikiform pour le composant
Ajoutez ceci dans l'onglet Fields du formulaire, pour que l'e-mail soit obligatoire et ref autorisé :
{
"version": 1,
"title": "Waitlist",
"fields": [
{ "id": "email", "type": "email", "label": "Email", "required": true },
{ "id": "ref", "type": "text", "label": "Source", "validation": { "max_length": 40 } }
]
}- Dans Allowed domains, ajoutez
your-project.framer.appet votre domaine personnalisé. N'ajoutez jamaisframer.appseul ; tous les sites Framer correspondraient. - Gardez la vérification anti-spam désactivée. Le composant envoie avec
fetchsans récupérer de jeton : un formulaire avec la vérification activée répond donc403 challenge_required. - Gardez les limites de débit Standard. Chaque visiteur est désormais compté séparément.
Vérifiez que tout fonctionne
- Publiez le site. Les webhooks et les code components n'envoient que depuis le site publié, pas depuis l'aperçu de l'éditeur.
- Envoyez le formulaire sur votre domaine
framer.app, puis à nouveau sur votre domaine personnalisé. - Ouvrez Submissions dans le tableau de bord. La nouvelle soumission y est, avec tous les champs envoyés.
- Pour le composant, ouvrez la page avec
?ref=testet vérifiez que l'inscription afficherefàtest. - 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. - 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ème | Que faire |
|---|---|
Toutes les inscriptions par webhook sont en spam avec origin_mismatch | Allowed domains est défini, et les envois des serveurs de Framer n'ont pas d'origine. Videz-le pour ce formulaire, ou passez au code component. |
| Framer signale des livraisons de webhook échouées | Ikiform a refusé l'envoi : 403 challenge_required avec la vérification anti-spam activée, ou 422 parce qu'un nom d'input n'est pas email. Corrigez la cause ; les nouvelles tentatives échouent de la même façon. |
429 rate_limited | Un 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. |
Le composant affiche Unknown field "ref" | La définition des champs ne liste pas ref. Ajoutez-le comme dans la définition ci-dessus. |
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. |
La configuration Framer complète, y compris la version formulaire de contact, est dans le guide Framer de la documentation. L'export de la liste et l'envoi de l'e-mail de lancement sont dans le guide principal.
Questions
Pas comme action de formulaire. Les formulaires Framer envoient vers les destinations de Framer, dont un webhook vers n'importe quelle URL https. Utilisez-le pour Ikiform, ou affichez votre propre formulaire avec un code component.
Avec la voie webhook, Framer envoie depuis ses serveurs sans en-tête Origin : un formulaire avec Allowed domains défini marque donc chaque envoi comme origin_mismatch. Laissez Allowed domains vide pour les formulaires à webhook.
Framer indique que les soumissions de formulaire sont illimitées : les inscriptions par webhook ne se heurtent donc à aucun plafond Framer. La voie code component n'utilise pas du tout les formulaires de Framer.
Non, il n'y a pas d'offre gratuite. Les tests sont gratuits : vous pouvez envoyer 50 soumissions de test par mois depuis localhost avant de payer. Pour passer en production, Pro commence à 6 $/mois pour 500 soumissions, ou vous pouvez payer une seule fois 10 000 soumissions pour 49 $ ou 50 000 soumissions pour 149 $, avec des crédits qui n'expirent jamais.
Une étape vous bloque, ou vous voulez un guide pour votre cas ? Écrivez à hello@ikiform.com.