新しい SaaS のウェイトリスト

新しい SaaS のウェイトリスト2 分で読めます

Framer のサイトにウェイトリストを追加する方法

Framer でウェイトリストの登録を集める 2 つの方法。Ikiform への Webhook を使う Framer のフォームか、訪問者ごとの制限とスパムチェックが残るコードコンポーネントです。

Preet SutharIkiform 創業者

Framer のフォームは外部の URL に送信できませんが、各送信データを Webhook に送れるので、ノーコードなら Ikiform のエンドポイントを Webhook にした Framer のフォームを使います。一般公開のローンチには、代わりに小さなコードコンポーネントを使ってください。ブラウザーから直接送信されるので、Ikiform の訪問者ごとのレート制限、許可ドメイン、ハニーポットがすべて機能し続けます。

ページ自体は午後だけで作れるので、ローンチ前のページの多くは Framer で作られています。わかりにくいのはウェイトリストのフォームです。Framer のフォームブロックは action を変更できないため、実際のトラフィックが来ると挙動が異なる 2 つの方法から選ぶことになります。

Framer ならではの違い

  • カスタムの action がありません。 Framer のフォームは、Framer 独自のストレージ、メール、Webhook のいずれかの送信先に送ります。コードなしで外部のバックエンドに届ける方法は Webhook だけです。
  • Webhook は訪問者ではなく Framer のサーバーから送られます。 Ikiform に見えるのは Framer の IP アドレスで、Origin ヘッダーはありません。そのため、許可ドメイン、訪問者ごとのレート制限、人間確認という 3 つの保護が無効になります。
  • リトライ。 Framer は 2xx 以外をすべて失敗とみなし、最大 5 回リトライします。リダイレクトはたどりません。Ikiform は JSON の送信に 201 を返すので、保存された登録は 1 回目で成功になります。
  • コードコンポーネントは本物の React です。 公開サイト上の訪問者のブラウザーで動くので、その中のフォームは他の Web ページと同じように送信します。
Framer のウェイトリスト登録が Ikiform に届く 2 つの経路Webhook 付きの Framer フォームコードコンポーネントFramer のフォームあなたのページ上Framer のサーバーJSON、最大 5 回リトライIkiformFramer の IP、originなしコードコンポーネントページ上の React フォーム訪問者のブラウザー冪等性キー付きの fetchIkiform訪問者とドメインが見える送信Webhook送信送信Framer のウェイトリスト登録が Ikiform に届く 2 つの経路Webhook 付きの Framer フォームコードコンポーネントFramer のフォームあなたのページ上Framer のサーバーJSON、最大 5 回リトライIkiformFramer の IP、origin なしコードコンポーネントページ上の React フォーム訪問者のブラウザー冪等性キー付きの fetchIkiform訪問者とドメインが見える送信Webhook送信送信
  1. Webhook の経路では、訪問者が Framer のフォームを送信し、Framer のサーバーがフィールドを JSON として Ikiform に転送します。
  2. Ikiform からは Framer のサーバーが送信者に見えるので、訪問者を区別したり、フォームがどのサイトにあるかを確認したりできません。
  3. コードコンポーネントでは、訪問者のブラウザーが Ikiform に直接送信します。
  4. Ikiform には訪問者ごとの IP とあなたのドメインが見えるので、すべての保護が適用されます。
どちらの経路でも保存される登録は同じです。違うのは、Ikiform が誰を送信者とみなすかです。

経路を選ぶ

ItemWebhook 付きの Framer フォームコードコンポーネント
設定右サイドバーの 2 つの項目コードファイル 1 つを 1 回貼り付け
スタイルFramer のフォームのスタイルと状態編集できるインラインスタイルか独自のクラス
訪問者ごとのレート制限いいえ。すべての送信が Framer の IP を共有はい、それぞれ 1 分あたり 5 件
許可ドメイン空のままにするFramer のドメインと独自ドメインを設定
ハニーポットいいえはい
人間確認オフオフ(fetch で送信するため)
流入元の記録不要URL の ?ref=、またはインスタンスごとの既定値

数百人に共有するページなら Webhook の経路で十分です。どこか大きな場所にページを投稿する予定なら、コードコンポーネントを使ってください。Framer のサーバー経由でボットの登録が一気に来ると、止められるのはスパムスコアだけです。

経路 1: Webhook 付きの Framer のフォーム

  1. ページにフォームを追加し、Email の入力欄を 1 つ置きます。入力欄の設定で名前を email にします。Framer は各入力欄の名前を JSON のキーとして送り、Ikiform は email を返信先と使い捨てドメインのチェックに使います。
  2. フォームを選択します。Send To の横で Add… › Webhook を選び、エンドポイント https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx を貼り付けます。署名シークレットは任意です。Ikiform は Framer の署名を確認しません。
  3. Ikiform では Allowed domains(許可ドメイン)を空のままにし、スパムチェックをオフにします。どちらかがオンだと、Framer からの送信には origin がなくブラウザーでの確認も実行できないため、すべての送信がスパム判定されるか拒否されます。
  4. レート制限を Relaxed に、ローンチ当日は Custom に設定します。Standard では Framer からの送信がすべて 1 人の訪問者として数えられ、フォーム全体で 1 分あたり 300 件が上限になります。
  5. フォームの成功時の表示を Framer でスタイリングします。Ikiform が 201 を返したときに表示されます。
フィールド定義があるのにメールアドレスがない場合など、Ikiform が拒否した送信は、Framer の 5 回のリトライでもすべて同じように失敗します。登録が届かなくなったら、Framer で配信を確認してください。

経路 2: コードコンポーネント

Assets パネルで Code を開き、WaitlistSignup という名前のコードファイルを作成して、これを貼り付けます。ページにドラッグし、プロパティパネルで 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" },
});
  • プロパティパネルには、ボタンのラベル、登録完了時のテキスト、既定の Source が表示されます。ヒーローに hero、フッターに footer のインスタンスを 1 つずつ置けば、どちらがコンバージョンにつながるかがわかります。
  • ページの URL の ?ref= は既定値より優先されるので、キャンペーンのリンクには自動でタグが付きます。
  • 冪等性キーは最初の送信時に作られ、同じ訪問中のリトライでは再利用されるので、スマートフォンでダブルタップしても保存される登録は 1 件です。
  • window は送信ハンドラーの中でだけ読みます。Framer はまずサーバーでコンポーネントをレンダリングするので、レンダリング中に window に触れるとそれが壊れます。

コンポーネント用の Ikiform の設定

メールアドレスを必須にし、ref を許可するために、フォームの Fields タブで次を追加します:

フィールド
{
  "version": 1,
  "title": "Waitlist",
  "fields": [
    { "id": "email", "type": "email", "label": "Email", "required": true },
    { "id": "ref", "type": "text", "label": "Source", "validation": { "max_length": 40 } }
  ]
}
  • Allowed domains に your-project.framer.app と独自ドメインを追加します。framer.app だけを追加してはいけません。すべての Framer サイトが一致してしまいます。
  • スパムチェックはオフのままにします。コンポーネントは fetch で送信し、トークンを取得しないので、チェックがオンのフォームは 403 challenge_required を返します。
  • レート制限は Standard のままにします。これで訪問者がそれぞれ個別に数えられます。

動作を確認する

  1. サイトを公開します。Webhook もコードコンポーネントも、エディターのプレビューからではなく、公開されたサイトからだけ送信します。
  2. framer.app ドメインでフォームを送信し、独自ドメインでももう一度送信します。
  3. ダッシュボードで Submissions を開きます。送ったすべてのフィールドを含む新しい送信データがあります。
  4. コンポーネントの場合は、?ref=test 付きでページを開き、登録の ref が test になっていることを確認します。
  5. 通知の受信者それぞれの受信箱を確認します。各自にフィールドを載せたメールが届き、フォームに email フィールドがあれば Reply は送信者宛てになります。
  6. 何も届かない場合は、ダッシュボードのステータスフィルターを Spam に切り替えてください。スパムにも本物の送信データと同じ成功の応答が返ります。

ハマりどころ

問題対処法
Webhook 経由の登録がすべて origin_mismatch でスパムに入るAllowed domains が設定されていますが、Framer のサーバーからの送信には origin がありません。このフォームでは設定を空にするか、コードコンポーネントに切り替えてください。
Framer が Webhook の配信失敗を報告するIkiform が送信を拒否しています。スパムチェックがオンで 403 challenge_required になっているか、入力欄の名前が email でないため 422 になっています。原因を直してください。リトライしても同じように失敗します。
429 rate_limited1 人の訪問者が、フォームのレート制限を超えて送信しました。Standard では訪問者ごとに 1 分あたり 5 件までです。Retry-After まで待つか、共有の 1 つの IP から送信するフォームには Relaxed か Custom を選んでください。
コンポーネントに Unknown field "ref" と表示されるフィールド定義に ref がありません。上の定義のように追加してください。
403 origin_not_allowed が返るか、すべての送信がスパムになるページのドメインがフォームの Allowed domains に入っていません。ドメインと、ローカルテスト用に localhost を追加してください。登録したドメインのサブドメインも許可されます。

お問い合わせフォーム版を含む Framer の設定の全体は、Framer のドキュメントガイドにあります。リストのエクスポートとローンチメールの送信はメインガイドで説明しています。

よくある質問

手順で詰まったときや、あなたの状況に合うガイドがほしいときはメールでどうぞ: hello@ikiform.com