# サーバーなしで、静的サイトにメール登録フォームを置く

> Hugo、Jekyll、Eleventy、Astro の静的サイトにニュースレター登録フォームを追加し、メールツールの API キーをページに入れず、確認はツールで行います。

公開日 2026-10-25 · https://ikiform.com/ja/guides/newsletter-signup/static-site

静的サイトには送信先となる自分のサーバーがないので、フォームは読者のブラウザーから直接 Ikiform に送信します。メールツールの API キーはサイトに一切入れないでください。ブラウザーが知っているのは公開されている Ikiform のエンドポイントだけで、別の場所でホストしたリレーが購読者を追加します。フォームはパーシャルやインクルードとして一度だけ書き、設置場所ごとに異なる `source` を渡し、再読み込みなしの版には小さなスクリプトを 1 つ追加します。

## ここが違う

静的サイトとは、ホスト上のファイルです。GitHub Pages、Netlify、Cloudflare Pages、S3 バケットなどです。読者がフォームを送信しても、自分で書いたものがサーバーで動くことはありません。そのため、ページからメールツールの API を呼び出すというよくある近道は使えません。ブラウザーで実行されるものは、誰でも読めるからです。

メールツールの API キーが、購読者 1 人の追加だけに限定されていることはまれです。購読者を追加できるキーは、たいていリスト全体の閲覧、エクスポート、削除もできます。静的サイトの JavaScript に入れると全訪問者に配布され、開発者ツールを開いた人なら誰でも手に入ります。

図: 静的サイトで、メールツールの API キーをどこに置くか

1. キーがページにあると、すべての訪問者がそれをダウンロードし、誰でもそのキーでメールツールの API を呼び出せます。
2. リレーを使えば、ページが知っているのは Ikiform のエンドポイントだけです。これはフォームの送信しか受け付けません。
3. Ikiform がスパムを除外し、署名付きの Webhook をリレーに送ります。
4. 静的サイトの外でホストされたリレーが API キーを保持し、購読者を追加します。

フォームのエンドポイントは公開しても安全です。API キーは決して公開してはいけません。

## フォームは一度だけ書く

ニュースレターのフォームは、フッター、各記事の末尾、ニュースレターのページなど、いくつものテンプレートに登場します。`source` の値を受け取るパーシャルにすれば、設置場所ごとに自分自身を記録できます。マークアップはどのジェネレーターでも同じで、変わるのは `source` のテンプレート構文だけです。

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

次に、設置場所の名前を付けてテンプレートに入れます。

**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" %}
```

- Astro では `<Newsletter source="post" />` を使います。静的な Astro のビルドでは Actions を実行できないので、このコンポーネントも他と同じく直接 Ikiform に送信します。
- Ikiform のフォームのフィールド定義に、`source` を最大長の短いテキストとして追加します。フィールドを定義している場合、定義にない入力欄は `unknown_field` で拒否されます。
- `_redirect` は、フォームの **Allowed domains** のいずれかにある `https:` の URL でなければなりません。受信トレイを確認するよう伝える 1 行を載せた `/subscribed/` ページを作ってください。

## ページ上のすべてのフォームに 1 つのスクリプト

記事のページには、記事の後とフッターの 2 つのニュースレターフォームがあることがあります。このスクリプトは `data-newsletter` の付いたすべてのフォームを処理し、1 つが成功するとすべてを置き換えるので、少し下でもう一度購読を求められることはありません。ベースレイアウトから一度だけ読み込んでください。

`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;
  });
}
```

- 厳しいネットワークフィルターの内側などで `fetch` が失敗した場合、スクリプトは `form.submit()` にフォールバックします。これは通常の方法で送信し、`/subscribed/` に着地します。
- `429` には専用のメッセージを表示します。同じオフィスやキャンパスのネットワークにいる読者は IP を共有しているので、訪問者ごとの制限にまとめて達することがあります。
- フォームは JavaScript がオフでも動きます。スクリプトは応答の表示方法を変えるだけです。

JavaScript がオフの読者は `/subscribed/` に着地するので、そのページでスクリプトが他の人にしていることを代わりに行いましょう。確認メールが送られていることを伝え、探すべき送信者名を示し、迷惑メールフォルダーにも触れ、ブログへのリンクを置きます。ジェネレーターで他のページと同じように作る普通のページで、あとでメールツールの確認リンクの遷移先にすることもできます。

## リレーをどこに置くか

リレーには、コードを実行できる公開の HTTPS URL が必要です。静的ホストによって、提供されている場合とされていない場合があります。

| 静的ホスト | リレーの選択肢 |
| --- | --- |
| GitHub Pages, S3 | 関数なし。カスタムドメインの Cloudflare Worker か、自動化ツールを使います |
| Netlify | サイトのドメインの Netlify Function、または上記のいずれか |
| Cloudflare Pages | カスタムドメインの別の Worker。Ikiform は `pages.dev` と `workers.dev` の URL を拒否します |
| Vercel | 自分のドメインの関数。静的な出力を API ルートと並べて置けます |

各メールツール用のリレーのコードは、[Kit](https://ikiform.com/ja/guides/newsletter-signup/kit.md)、[MailerLite](https://ikiform.com/ja/guides/newsletter-signup/mailerlite.md)、[beehiiv](https://ikiform.com/ja/guides/newsletter-signup/beehiiv.md) のページにあります。コードを一切書かないなら、Webhook を [Zapier](https://ikiform.com/blog/form-submissions-to-zapier.md) か [n8n](https://ikiform.com/blog/form-submissions-to-n8n.md) に向け、そのメールツール用のアクションを使ってください。URL のルールは [Webhook のドキュメント](https://ikiform.com/ja/docs/webhooks.md)にあります。

> 静的サイトには、別ドメインのデプロイプレビューがあることがよくあります。**Allowed domains** にないドメインからの送信はスパムとしてマークされるか拒否されるので、テスト中はプレビューのドメインを追加するか、本番のドメインと `localhost` でテストしてください。

## サイトが Content Security Policy を送っている場合

多くの静的ホストでは、Netlify や Cloudflare Pages の `_headers` のように、ファイルからヘッダーを設定できます。Content-Security-Policy を送っている場合、ポリシーが Ikiform を許可していなければブラウザーはフォームをブロックします。`form-action` はプレーンな送信を、`connect-src` はスクリプトの `fetch` をカバーします。

`_headers`:

```text
Content-Security-Policy: default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com
```

ブロックされた送信はブラウザーのコンソールに CSP エラーとして表示され、Ikiform には届かないので、ダッシュボードにも何も表示されません。

## 動作を確認する

1. 本番のドメインと `localhost` を **Allowed domains** に追加します。
2. サイトをビルドして記事のページを開きます。両方のフォームがあり、ページのソースではそれぞれ別の `source` の値を持っています。
3. 記事のフォームから自分のアドレスで購読します。両方のフォームが、受信トレイを確認するよう伝える 1 行に変わります。
4. JavaScript をオフにしてフッターから購読します。`/subscribed/` に着地します。
5. ダッシュボードで **Submissions** を開きます。2 件の登録の `source` は `post` と `footer` になっています。
6. ビルドしたサイトでメールツールの API キーを検索します。見つかりません。

## ハマりどころ

| 問題 | 対処法 |
| --- | --- |
| ダッシュボードに何もなく、コンソールに CSP エラーがある | Content-Security-Policy の `form-action` と `connect-src` に `https://ikiform.com` を追加してください。 |
| `source` で `unknown_field` の `422` | フォームのフィールド定義に `source` を追加するか、非表示の入力欄を削除してください。 |
| `403 origin_not_allowed` が返る、またはすべての送信がスパムになる | ページのドメインがフォームの **Allowed domains** にありません。ドメインと、ローカルテスト用の `localhost` を追加してください。登録したドメインのサブドメインは許可されます。 |
| ブラウザーが自分のページではなく Ikiform のサンクスページに移動する | `_redirect` は、ドメインが **Allowed domains** にあり、`https:` の URL でないと無視されます。ドメインを追加するか、代わりにフォーム設定で **Redirect after submit** を設定してください。 |

## よくある質問

### バックエンドなしで静的サイトにニュースレター登録フォームを追加できますか？

はい。フォームはブラウザーから Ikiform などのフォームバックエンドに送信し、そこで各登録を保存してフィルタリングします。別の場所でホストしたリレーや自動化ツールがそれをメールツールに渡すので、静的サイト自体はサーバーのコードを実行しません。

### 静的なページから直接メールツールの API を呼び出せますか？

やめてください。ページの JavaScript にあるキーはすべての訪問者に見え、メールツールのキーはたいていリスト全体を読んだり変更したりできます。キーはサイトの外で動くリレーに置いてください。

### GitHub Pages で使えますか？

はい。フォームはプレーンな HTML で、Ikiform に送信します。GitHub Pages ではリレーを動かせないので、カスタムドメインの Cloudflare Worker でホストするか、Zapier や n8n などの自動化ツールを使ってください。

### 確認メールは誰が送りますか？

メールツールです。Ikiform は登録した本人にメールを送りません。リレーが読者を未確認として追加し、ツールが独自の確認リンクを送ります。

## その他のガイド

- [ニュースレター登録フォーム](https://ikiform.com/ja/guides/newsletter-signup.md): 自分のマークアップでニュースレター登録フォームを作り、同意を記録し、ボットの登録を防ぎ、購読者を Kit、MailerLite、beehiiv など任意のツールに渡します。
- [ダブルオプトイン](https://ikiform.com/ja/guides/newsletter-signup/double-opt-in.md): Ikiform は確認メールを送らないので、ダブルオプトインはメールツールで行います。中継が各登録を未確認として追加し、ツールがリンクを送ります。
- [Kit (ConvertKit)](https://ikiform.com/ja/guides/newsletter-signup/kit.md): 購読者を作成し、特典メールのために Kit のフォームに追加し、設置場所のタグを付けるリレーで、自作のニュースレターフォームを Kit につなぎます。
- [beehiiv](https://ikiform.com/ja/guides/newsletter-signup/beehiiv.md): 自作の登録フォームを beehiiv につなぎます。リレーがダブルオプトインをオンにし、設置場所を UTM ソースに、ページをリファラーにして各購読を作成します。
- [MailerLite](https://ikiform.com/ja/guides/newsletter-signup/mailerlite.md): 自作の登録フォームを MailerLite につなぎます。リレーが新しい読者をダブルオプトインのために未確認として追加し、購読者に二度確認を求めることはありません。
- [Next.js のニュースレターフォーム](https://ikiform.com/ja/guides/newsletter-signup/nextjs.md): Next.js のフッターのニュースレターフォームです。同意のチェックと Turnstile を行うサーバーアクションが Ikiform に送信し、ルートハンドラーが読者を Kit に追加します。
