# プレーンな HTML でランディングページにウェイトリストフォームを追加する方法

> ビルド不要でプレーンな HTML だけのウェイトリストページ。JavaScript がオフでも動き、読み込めばその場でお礼を表示し、必要なときはフォールバックします。

公開日 2026-10-25 · https://ikiform.com/ja/guides/waitlist/html

メールアドレスを Ikiform のエンドポイントに送信してサンクスページにリダイレクトする普通の HTML フォームを書けば、JavaScript なしでどのホストでも動きます。そのうえで、同じフォームを `fetch` で送信してその場でお礼を表示する小さなスクリプトを追加し、人間確認がオンのときなどスクリプトで完了できない場合は通常の送信に戻します。

ウェイトリストのページに、フレームワークもビルドもサーバーも要りません。4 つの静的ファイルとフォームバックエンドがあれば十分で、ビルダーが書き出すどんなページよりも速く読み込まれます。このページでは全体を 1 層ずつ組み立て、各層が単独でも動くようにします。

## ビルドなしで大切なこと

- **フォームそのものが連携です。** `action` を持つ素の `<form>` だけで Ikiform とつながります。パッケージも API キーもコンパイルも要りません。
- **JavaScript は必須ではなく、上乗せです。** スクリプトがブロックされても、遅くても、壊れていても、ページは登録を受け付けるべきです。以下の各層は前の層に機能を足し、失敗したら前の層に戻ります。
- **スクリプトで訪問者を行き詰まらせてはいけません。** `fetch` が失敗したり、`fetch` では実行できない人間確認を Ikiform が求めたりしたら、スクリプトはフォームをブラウザーに返し、通常の方法で送信させます。
- **ホスティングはどこでも構いません。** GitHub Pages、Netlify、Cloudflare Pages、S3 バケット、共有ホスティングのどれでも、これらのファイルをそのまま配信できます。

| 層 | 追加されるもの | ない場合 |
| --- | --- | --- |
| HTML フォーム | メールアドレスを送信し、`thanks.html` にリダイレクト | 何も動かないので、最初に作る |
| CSS | ライトモードとダークモードで読みやすいページ | 簡素だが動くフォーム |
| スクリプト | その場でのお礼、フィールドエラー、ページ遷移なし | 通常の送信とリダイレクト |

図: プレーンな HTML のウェイトリストページで登録がたどる 3 つの経路

1. JavaScript がオフなら、ブラウザーがフォームを送信し、Ikiform が _redirect の URL に 303 でリダイレクトし、訪問者には thanks.html が表示されます。
2. スクリプトが読み込まれていれば、fetch で送信して JSON を要求します。Ikiform は 201 かエラーを返し、スクリプトが同じページに結果を表示します。
3. fetch が失敗したり、Ikiform が challenge_required を返したりした場合、スクリプトは form.submit() を呼びます。
4. その通常の送信で、必要なら Ikiform の確認ページを経て、thanks.html に到着します。

どの経路でも最後に登録が保存されます。スクリプトが変えるのは訪問者への見え方だけです。

## ファイル

### ページ

`index.html`:

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Acme is almost here</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main>
    <h1>Acme is almost here</h1>
    <p>Invoices that chase themselves. Leave your email and we'll invite you first.</p>

    <form id="waitlist" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
      <label for="email">Email</label>
      <div class="row">
        <input id="email" name="email" type="email" autocomplete="email" required placeholder="you@company.com">
        <button>Join the waitlist</button>
      </div>
      <input type="hidden" name="_redirect" value="https://example.com/thanks.html">
      <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden>
      <p class="message" role="status" aria-live="polite"></p>
    </form>
  </main>
  <script src="waitlist.js" defer></script>
</body>
</html>
```

- `_redirect` は、通常の送信の後にブラウザーを `thanks.html` に移動させます。フォームの **Allowed domains** にある `https:` の URL でしか機能しないので、そこにドメインを追加してください。フォーム設定の **Redirect after submit** を使えば、hidden の入力欄なしで同じことができます。
- `_gotcha` 入力欄はハニーポットです。人には見えず、すべての入力欄を埋めるボットはスパムとして保存されます。
- `aria-live="polite"` により、スクリーンリーダーはフォーカスを移さずにスクリプトのメッセージを読み上げます。
- script タグに `defer` を付けているので、フォームができてから実行され、ページの描画を妨げません。

### 機能の上乗せ

`waitlist.js`:

```js
const form = document.getElementById("waitlist");
const message = form.querySelector(".message");
const button = form.querySelector("button");
const key = crypto.randomUUID();

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  button.disabled = true;
  message.textContent = "Joining…";
  try {
    const response = await fetch(form.action, {
      method: "POST",
      headers: { Accept: "application/json", "Idempotency-Key": key },
      body: new FormData(form),
    });
    const result = await response.json();
    if (result.ok) {
      form.replaceChildren(Object.assign(document.createElement("p"), {
        textContent: "You're on the list. We'll email you on launch day.",
        role: "status",
      }));
      return;
    }
    if (result.error.code === "challenge_required") {
      form.submit();
      return;
    }
    const wait = response.headers.get("Retry-After");
    message.textContent = wait
      ? `Too many tries. Wait ${wait} seconds and try again.`
      : result.error.fields?.email?.[0] ?? result.error.message;
  } catch {
    form.submit();
    return;
  }
  button.disabled = false;
});
```

- `Accept: application/json` を付けると Ikiform がリダイレクトではなく JSON で応答するので、スクリプトがその場に結果を表示できます。
- 冪等性キーはページの読み込みごとに 1 回作られます。ダブルクリックや、応答が遅いときのリトライでも、登録は 1 件だけ保存されます。
- `challenge_required` は、フォームのスパムチェックが **Ikiform** に設定されていて、`fetch` がトークンを送らなかったことを意味します。`form.submit()` は通常の方法で送信するので、Ikiform の確認ページ、続いてサンクスページが表示されます。`submit` イベントは再び発火しないので、ループにはなりません。
- ネットワークエラーでも同じフォールバックを使います。Ikiform に到達できなかった場合、通常の送信ではブラウザー自身のエラーが表示されますが、止まったままのボタンよりは正直です。
- `429` では `Retry-After` を読み、どれだけ待てばよいかを表示します。

### サンクスページ

`thanks.html`:

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>You're on the list</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main>
    <h1>You're on the list</h1>
    <p>We'll email you the day Acme opens. Nothing before that.</p>
    <p><a href="/">Back to the page</a></p>
  </main>
</body>
</html>
```

### スタイル

`style.css`:

```text
body { margin: 0; font: 16px/1.5 system-ui, sans-serif; color: #111; background: #fff; }
main { max-width: 34rem; margin: 0 auto; padding: 18vh 1rem 4rem; }
h1 { font-size: 2.25rem; line-height: 1.1; margin: 0 0 0.75rem; }
label { display: block; font-weight: 600; margin: 1.5rem 0 0.375rem; }
.row { display: flex; flex-wrap: wrap; gap: 0.5rem; }
input[type="email"] { flex: 1 1 14rem; font: inherit; padding: 0.625rem 0.75rem; border: 1px solid #ccc; border-radius: 0.5rem; }
button { font: inherit; padding: 0.625rem 1rem; border: 0; border-radius: 0.5rem; background: #111; color: #fff; cursor: pointer; }
button:disabled { opacity: 0.6; cursor: progress; }
.message { min-height: 1.5em; margin: 0.5rem 0 0; }
@media (prefers-color-scheme: dark) {
  body { color: #eee; background: #111; }
  input[type="email"] { background: #1b1b1b; color: inherit; border-color: #444; }
  button { background: #eee; color: #111; }
}
```

> ページが `example.com` 以外のドメインにある場合は、`_redirect` と戻るリンクを更新してください。Ikiform はたどれない `_redirect` を無視し、代わりに自身がホストするサンクスページを表示するので、不一致があっても登録が失われることはありません。

## デプロイとテスト

4 つのファイルを 1 つのフォルダーに入れ、任意の静的ホストにアップロードします。次に、`username.github.io`、`your-site.netlify.app`、自分のドメインなど、ホストのドメインを **Allowed domains** に追加します。ローカルで作業する間は `localhost` も追加し、任意の静的サーバーでフォルダーを配信してください。`file://` の URL で開いたページはオリジンを送らないためです。

## 動作を確認する

1. フォームの **Allowed domains** に、自分のサイトのドメインと、開発中は `localhost` を追加します。空にしておくと、どこからの送信も受け付けます。
2. ブラウザーで JavaScript をオフにして登録します。`thanks.html` に移動します。
3. オンに戻してもう一度登録します。ページを離れずに、フォームがお礼の一文に置き換わります。
4. **Spam check** を **Ikiform** に設定して、もう一度登録します。ページは確認に移り、その後 `thanks.html` に移動します。
5. ダッシュボードで **Submissions** を開きます。送ったすべてのフィールドを含む新しい送信データがあります。
6. ハニーポットに値を入れて `_gotcha=bot` で送信します。成功の応答は返りますが、送信データはスパムとして保存され、メールも Webhook も送られません。

## ハマりどころ

| 問題 | 対処法 |
| --- | --- |
| ブラウザーが自分のページではなく Ikiform のサンクスページに移動する | `_redirect` は、ドメインが **Allowed domains** にあり、`https:` の URL でない限り無視されます。ドメインを追加するか、代わりにフォームの設定で **Redirect after submit** を設定してください。 |
| `403 origin_not_allowed` が返るか、すべての送信がスパムになる | ページのドメインがフォームの **Allowed domains** に入っていません。ドメインと、ローカルテスト用に `localhost` を追加してください。登録したドメインのサブドメインも許可されます。 |
| ファイルとして開いたページからの送信が拒否される、またはスパムになる | `file://` のページはオリジンを送りません。ローカルの静的サーバーでフォルダーを配信し、`localhost` を **Allowed domains** に追加してください。 |
| 登録は届くのに、お礼の一文が表示されない | スクリプトが読み込まれていません。`src` 属性のパスや、ホストが `.js` ファイルを配信しているかを確認してください。通常の送信は動き続けるので、登録は届きます。 |
| `402 test_limit_reached` | ワークスペースにプランがなく、今月の無料の `localhost` テスト 50 件を使い切っています。プランを選ぶか、ダッシュボードの **Send test** を使ってください。 |

予約済みフィールドの一覧は [HTML フォームのドキュメント](https://ikiform.com/ja/docs/html.md)に、名前と役割付きの版は[ウェイトリストのテンプレート](https://ikiform.com/forms/waitlist)にあります。リストのエクスポートとローンチメールについては[メインガイド](https://ikiform.com/ja/guides/waitlist.md#launch-day)を参照してください。

## よくある質問

### ウェイトリストフォームは JavaScript なしで動きますか？

はい。プレーンな HTML フォームがメールアドレスを Ikiform のエンドポイントに送信し、Ikiform がブラウザーをサンクスページにリダイレクトします。JavaScript が加えるのは、その場でのメッセージ表示だけです。

### プレーンな HTML のウェイトリストページはどこでホストできますか？

静的ファイルを配信できる場所ならどこでも構いません。GitHub Pages、Netlify、Cloudflare Pages、S3、共有ホスティングなどです。フォームは Ikiform に送信されるので、ホストがコードを実行することはありません。

### 人間確認がオンのときにスクリプトが fetch で送信するとどうなりますか？

Ikiform は 403 challenge_required を返します。このガイドのスクリプトはそれを捕まえて通常の方法でフォームを送信するので、確認ページ、続いてサンクスページが表示されます。

### Ikiform はフォームのページをホストしてくれますか？

いいえ。Ikiform にホスト型のフォームページはありません。任意のビルダーやフレームワークを使って自分のサイトにフォームを置き、送信先を Ikiform のエンドポイントにします。

### Ikiform は無料ですか？

いいえ、無料プランはありません。テストは無料で、支払う前に localhost からテスト送信を月 50 件まで送れます。本番運用には、500 件で月 $6 からの Pro か、買い切りで 10,000 件 $49、または 50,000 件 $149 を選べます。クレジットに有効期限はありません。

## その他のガイド

- [新しい SaaS のウェイトリスト](https://ikiform.com/ja/guides/waitlist.md): 自分のランディングページにローンチ前のウェイトリストを作ります。フォーム、スパム対策、重複、確認メール、ローンチ日のきれいなエクスポートまで扱います。
- [Next.js のウェイトリスト](https://ikiform.com/ja/guides/waitlist/nextjs.md): Next.js App Router のウェイトリスト。シークレットキーで登録を転送するサーバーアクション、useActionState、楽観的なサンクス表示、訪問者ごとの制限を扱います。
- [Carrd のウェイトリスト](https://ikiform.com/ja/guides/waitlist/carrd.md): Carrd のページでウェイトリストの登録を集める方法。Pro Plus では UTM を記録する Send to URL フォーム、Pro Standard ではハニーポット付きの埋め込み HTML フォームを使います。
- [Product Hunt ローンチリスト](https://ikiform.com/ja/guides/waitlist/product-hunt.md): Product Hunt の数週間前からローンチリストを集め、登録をチャネルごとにタグ付けし、太平洋時間のリセットに合わせてローンチ日のメールを 2 回に分けて送ります。
- [選考付きのベータ登録](https://ikiform.com/ja/guides/waitlist/beta-signup.md): サーバーが必須を強制する選考質問付きのベータ応募フォームと、応募者を採点してリストから各コホートを選ぶスクリプトを紹介します。
- [モバイルアプリのウェイトリスト](https://ikiform.com/ja/guides/waitlist/mobile-app.md): 使っているスマホを尋ね、iPhone のテスターを TestFlight に送り、Play のクローズドテストを埋める、iOS・Android アプリ向けの公開前ウェイトリストです。
