Warteliste für ein neues SaaS

Warteliste für ein neues SaaS6 Min. Lesezeit

So fügst du einer Framer-Site eine Warteliste hinzu

Zwei Wege, auf Framer Wartelisten-Anmeldungen zu sammeln: Framers Formular mit einem Webhook an Ikiform oder eine Code-Komponente, die Limits pro Besucher und Spamprüfungen behält.

Preet SutharGründer, Ikiform

Framer-Formulare können nicht an eine externe URL senden, aber jede Einsendung an einen Webhook schicken, der No-Code-Weg ist also Framers Formular mit deinem Ikiform-Endpunkt als Webhook. Für einen öffentlichen Launch nimm stattdessen eine kleine Code-Komponente: Der Browser sendet direkt, Ikiforms Rate-Limit pro Besucher, erlaubte Domains und der Honeypot funktionieren also weiter.

Auf Framer entstehen viele Pre-Launch-Seiten, weil die Seite selbst einen Nachmittag dauert. Beim Wartelisten-Formular wird es weniger offensichtlich. Framers Formularblock kann seine action nicht ändern, du wählst also zwischen zwei Wegen, die sich unterschiedlich verhalten, sobald echter Traffic kommt.

Was auf Framer anders ist

  • Keine eigene action. Ein Framer-Formular sendet an Framers Ziele: seinen eigenen Speicher, E-Mail oder einen Webhook. Ein Webhook ist der einzige Weg, ohne Code ein externes Backend zu erreichen.
  • Webhooks kommen von Framers Servern, nicht vom Besucher. Ikiform sieht Framers IP-Adressen und keinen Origin-Header. Das schaltet drei Schutzmechanismen ab: erlaubte Domains, das Rate-Limit pro Besucher und die Menschlichkeitsprüfung.
  • Wiederholungen. Framer behandelt alles außer einem 2xx als Fehler und versucht es bis zu fünfmal erneut. Es folgt keinen Weiterleitungen. Ikiform beantwortet einen JSON-Post mit 201, eine gespeicherte Anmeldung ist also beim ersten Mal ein Erfolg.
  • Code-Komponenten sind echtes React. Sie laufen auf der veröffentlichten Site im Browser des Besuchers, ein Formular darin sendet also wie jede andere Webseite.
Zwei Wege, wie eine Framer-Wartelisten-Anmeldung Ikiform erreichtFramer-Formular mit WebhookCode-KomponenteFramer-Formularauf deiner SeiteFramer-ServerJSON, bis zu 5WiederholungenIkiformsieht Framers IP,kein OriginCode-KomponenteReact-Formular aufdeiner SeiteBrowser desBesuchersfetch mitIdempotenzschlüsselIkiformsieht den Besucherund deine DomainabsendenWebhookabsendenPostZwei Wege, wie eine Framer-Wartelisten-Anmeldung Ikiform erreichtFramer-Formular mit WebhookCode-KomponenteFramer-Formularauf deiner SeiteFramer-ServerJSON, bis zu 5 WiederholungenIkiformsieht Framers IP, kein OriginCode-KomponenteReact-Formular auf deiner SeiteBrowser des Besuchersfetch mit IdempotenzschlüsselIkiformsieht den Besucher und deineDomainabsendenWebhookabsendenPost
  1. Beim Webhook-Weg sendet der Besucher Framers Formular ab, und Framers Server leiten die Felder als JSON an Ikiform weiter.
  2. Ikiform sieht Framers Server als Absender, kann also Besucher nicht unterscheiden oder prüfen, auf welcher Site das Formular steht.
  3. Bei der Code-Komponente sendet der Browser des Besuchers direkt an Ikiform.
  4. Ikiform sieht die IP jedes Besuchers und deine Domain, also greift jeder Schutz.
Beide Wege speichern dieselbe Anmeldung. Sie unterscheiden sich darin, wen Ikiform für den Absender hält.

Einen Weg wählen

ItemFramer-Formular mit WebhookCode-Komponente
EinrichtungZwei Felder in der rechten SeitenleisteEine Code-Datei, einmal eingefügt
GestaltungFramers Formular-Styles und -ZuständeInline-Styles, die du bearbeitest, oder eigene Klassen
Rate-Limit pro BesucherNein. Alle Posts teilen sich Framers IPsJa, je 5 pro Minute
Erlaubte DomainsLeer lassenAuf deine Framer- und eigenen Domains setzen
HoneypotNeinJa
MenschlichkeitsprüfungAusAus, weil sie mit fetch sendet
Quellen-TrackingKeine?ref= aus der URL oder ein Standardwert pro Instanz

Der Webhook-Weg ist in Ordnung für eine Seite, die du mit ein paar Hundert Leuten teilst. Wenn du die Seite gleich irgendwo Großes postest, nimm die Code-Komponente: Einen Schub Bot-Anmeldungen über Framers Server kann nur die Spam-Bewertung stoppen.

Weg 1: Framers Formular mit einem Webhook

  1. Füge der Seite ein Formular mit einem Email-Eingabefeld hinzu. Setze in den Einstellungen des Felds seinen Namen auf email. Framer sendet den Namen jedes Felds als JSON-Schlüssel, und Ikiform nutzt email für Reply-to und die Prüfung auf Wegwerf-Domains.
  2. Wähle das Formular aus. Wähle neben Send To Add… › Webhook und füge deinen Endpunkt https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx ein. Das Signing-Secret ist optional; Ikiform prüft Framers Signatur nicht.
  3. Lass in Ikiform Allowed domains leer und die Spamprüfung aus. Ist eines davon aktiv, wird jeder Framer-Post markiert oder abgelehnt, weil er keinen Origin trägt und keine Browser-Prüfung ausführen kann.
  4. Setze das Rate-Limit auf Relaxed, oder am Launch-Tag auf Custom. Standard zählt alle Posts von Framer als einen Besucher, und das ganze Formular ist auf 300 pro Minute begrenzt.
  5. Gestalte den Erfolgszustand des Formulars in Framer. Er erscheint, wenn Ikiform mit 201 antwortet.
Ein Post, den Ikiform ablehnt, etwa eine fehlende E-Mail bei vorhandener Felddefinition, scheitert bei allen fünf Wiederholungen von Framer gleich. Prüfe die Zustellung in Framer, wenn keine Anmeldungen mehr ankommen.

Weg 2: eine Code-Komponente

Öffne im Panel Assets den Bereich Code, erstelle eine Code-Datei namens WaitlistSignup und füge das hier ein. Zieh sie auf die Seite und setze im Eigenschaften-Panel Form ID.

WaitlistSignup.tsx
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" },
});
  • Das Eigenschaften-Panel bietet die Button-Beschriftung, den Text nach dem Eintragen und eine Standard-Source. Setz eine Instanz mit hero in den Hero-Bereich und eine weitere mit footer in den Footer, und du siehst, welche besser konvertiert.
  • Ein ?ref= in der Seiten-URL gewinnt gegen den Standardwert, Kampagnenlinks markieren sich also selbst.
  • Der Idempotenzschlüssel wird beim ersten Absenden erzeugt und bei Wiederholungen aus demselben Besuch wiederverwendet, ein Doppeltipp am Handy speichert also eine Anmeldung.
  • window wird nur im Submit-Handler gelesen. Framer rendert Komponenten zuerst auf dem Server, und ein Zugriff auf window beim Rendern würde das kaputt machen.

Ikiform-Einstellungen für die Komponente

Füge diese im Tab Fields des Formulars hinzu, damit die E-Mail Pflicht ist und ref erlaubt ist:

Felder
{
  "version": 1,
  "title": "Waitlist",
  "fields": [
    { "id": "email", "type": "email", "label": "Email", "required": true },
    { "id": "ref", "type": "text", "label": "Source", "validation": { "max_length": 40 } }
  ]
}
  • Füge unter Allowed domains your-project.framer.app und deine eigene Domain hinzu. Füge nie framer.app allein hinzu; jede Framer-Site würde passen.
  • Lass die Spamprüfung aus. Die Komponente sendet mit fetch und holt kein Token, ein Formular mit aktiver Prüfung antwortet also mit 403 challenge_required.
  • Behalte die Standard-Rate-Limits. Jeder Besucher wird jetzt einzeln gezählt.

Prüfen, ob es funktioniert

  1. Veröffentliche die Site. Webhooks und Code-Komponenten senden nur von der veröffentlichten Site, nicht aus der Vorschau im Editor.
  2. Sende das Formular auf deiner framer.app-Domain ab und noch einmal auf deiner eigenen Domain.
  3. Öffne Submissions im Dashboard. Die neue Einsendung ist da, mit jedem Feld, das du gesendet hast.
  4. Öffne für die Komponente die Seite mit ?ref=test und prüfe, dass die Anmeldung ref als test zeigt.
  5. Prüfe das Postfach jedes Benachrichtigungsempfängers. Jeder bekommt eine E-Mail mit den Feldern, und Reply geht an den Absender, wenn das Formular ein email-Feld hat.
  6. Nichts angekommen? Stell den Statusfilter im Dashboard auf Spam. Spam bekommt dieselbe Erfolgsantwort wie eine echte Einsendung.

Stolperfallen

ProblemWas zu tun ist
Jede Webhook-Anmeldung liegt mit origin_mismatch im SpamAllowed domains ist gesetzt, und Framers Server-Posts haben keinen Origin. Leere es für dieses Formular oder wechsle zur Code-Komponente.
Framer meldet fehlgeschlagene Webhook-ZustellungenIkiform hat den Post abgelehnt: 403 challenge_required bei aktiver Spamprüfung oder 422, weil ein Feldname nicht email ist. Beheb die Ursache; Wiederholungen scheitern genauso.
429 rate_limitedEin Besucher hat mehr gesendet, als das Rate-Limit des Formulars erlaubt. Standard erlaubt 5 pro Minute und Besucher. Warte Retry-After ab oder wähle Relaxed oder Custom für Formulare, die von einer gemeinsamen IP aus senden.
Die Komponente zeigt Unknown field "ref"Die Felddefinition listet ref nicht auf. Füge es wie in der Definition oben hinzu.
403 origin_not_allowed, oder jeder Post landet im SpamDie Domain der Seite steht nicht unter Allowed domains des Formulars. Füge sie hinzu, plus localhost für lokale Tests. Subdomains einer eingetragenen Domain sind erlaubt.

Die vollständige Framer-Einrichtung, einschließlich der Kontaktformular-Version, steht in der Framer-Anleitung in der Doku. Export der Liste und Versand der Launch-E-Mail stehen in der Hauptanleitung.

Fragen

Hängst du bei einem Schritt fest oder willst eine Anleitung für deinen Fall? Schreib an hello@ikiform.com.