静的サイトのお問い合わせフォーム2 分で読めます
Netlify Forms を使わずに Netlify にお問い合わせフォームを追加する
Netlify Forms を使わずに、Netlify のサイトでお問い合わせフォームを運用します。ビルド時の検出なし、React からの JSON 送信、デプロイプレビュー用の別フォーム。
フォームに data-netlify を付けず、action を Ikiform のエンドポイントに向けます。Netlify はページを配信するだけで、送信には一切関わりません。React や Vue のフォームに hidden の HTML のコピーは不要で、JSON で送信できます。プレビューのホストは Allowed domains でサイトと一致しないので、netlify.toml でデプロイプレビュー用に別のフォーム ID を設定してください。
Netlify ならではの違い
Netlify は、主要な静的ホストの中で唯一フォーム機能を内蔵しています。Netlify Forms はビルドのたびにデプロイされた HTML を読み、data-netlify か netlify の付いたフォームを見つけて、そのフォームについては自分のサイトへの送信を受け付けます。それ以外の静的ページへの送信は失敗し、Netlify では 405 ではなく 404 で失敗します:
curl -i -X POST -d "message=hello" https://your-site.netlify.app/contact/HTTP/2 404
server: Netlify
cache-status: "Netlify Edge"; fwd=method; fwd-status=404
content-length: 0この 404 のせいで、実は存在するページを探し回ることになります。これは、検出されたフォームのどれも送信と一致しなかったという意味です。そもそも Netlify Forms を使うかどうかは別の問題で、Ikiform と Netlify Forms の比較で率直に答えています。サイトがずっと Netlify にあり、フォームがプレーンな HTML なら、Netlify Forms のほうが手間はかかりません。このページはそれ以外の場合のためのものです。
| あなたの状況 | Netlify Forms が邪魔になる理由 |
|---|---|
| フォームを React、Vue、Svelte で描画している | ビルドは静的な HTML を解析するので、クライアントで描画するフォームには Netlify が見つけられる hidden の複製が必要 |
fetch から JSON を送信したい | Netlify Forms は urlencoded と multipart のボディしか受け付けない |
| サイトを別のホストに移すかもしれない | フォームは自分のドメインに送信するので、Netlify がそのドメインを配信している間しか動かない |
| メッセージを、検証できる Webhook に届けたい | Ikiform は各配信に Standard Webhooks のヘッダーで署名し、リトライする |
- Netlify Forms では、Netlify がデプロイごとに HTML を走査し、data-netlify の付いたフォームを探します。
- 見つかったフォームは自分のサイトへの送信を受け付け、Netlify がそれを保存します。
- hidden のコピーなしで JavaScript が描画したフォームなど、見つからなかったフォームは、どの送信も 404 になります。
- Ikiform なら、ビルド時に何かを見つけてもらう必要はありません。
- フォームは HTML か JSON で ikiform.com に直接送信し、Ikiform が保存してメールで通知します。
フォームを Netlify Forms から外す
- フォームから
data-netlifyかnetlify、netlify-honeypot、data-netlify-recaptchaを削除し、hidden のform-name入力欄も削除します。form-nameを残すと、普通のフィールドとして保存されます。 - 下のような、JavaScript フォームの hidden の静的コピーを削除します。Netlify がフォームを検出するためだけに必要だったものです。
action(またはfetchの URL)を Ikiform のエンドポイントに設定します。以前のaction="/thanks"は、フォームの Redirect after submit 設定に移します。- 先に Netlify の Forms ページから過去の送信データをダウンロードしてください。自動では移行されません。
<form name="contact" netlify hidden>
<input name="name" />
<input name="email" />
<textarea name="message"></textarea>
</form>サイトの他のフォームがまだ Netlify Forms を使っているなら、form detection はオンのままにしてください。Netlify のドキュメントによれば、検出をオフにするのは Netlify Forms を使わないサイトだけが対象です。どこからも使わなくなったらオフにすれば、デプロイごとの走査を省けます。
Netlify のスパム設定の代わりになるもの
Netlify は送信に Akismet を実行し、netlify-honeypot と reCAPTCHA を提供しています。Ikiform では、フォームの Honeypot field を bot-field など今使っている名前に設定すれば、ハニーポットはそのまま機能します。スパムスコアは設定なしですべての送信に対して実行され、Spam check を Ikiform にすれば、キー不要の Cloudflare Turnstile が reCAPTCHA の代わりになります。切り替えるときは reCAPTCHA の div を削除してください。
JSON を送信する React のフォーム
Netlify Forms がなければ、クライアントで描画するフォームに複製は不要で、JSON も送れます。JSON なら型が保たれ、先に値をトリムすることもできます。このフォームは、ページをビルドした Netlify のコンテキストも記録します:
import { type FormEvent, useState } from "react";
const ENDPOINT = `https://ikiform.com/f/${import.meta.env.VITE_IKIFORM_FORM_ID}`;
interface Message {
name: string;
email: string;
message: string;
context: string;
}
export function ContactForm() {
const [sent, setSent] = useState(false);
const [error, setError] = useState("");
async function onSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const data = new FormData(event.currentTarget);
const body: Message & Record<string, string> = {
name: String(data.get("name") ?? "").trim(),
email: String(data.get("email") ?? "").trim(),
message: String(data.get("message") ?? "").trim(),
context: import.meta.env.VITE_CONTEXT ?? "local",
_gotcha: String(data.get("_gotcha") ?? ""),
};
setError("");
const response = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify(body),
}).catch(() => null);
const result = await response?.json().catch(() => null);
if (result?.ok) return setSent(true);
const fields: Record<string, string[]> = result?.error?.fields ?? {};
setError(Object.values(fields)[0]?.[0] ?? result?.error?.message ?? "Couldn't send. Try again.");
}
if (sent) return <p role="status">Got it. I'll answer from my own inbox, usually the same day.</p>;
return (
<form onSubmit={onSubmit}>
<label htmlFor="name">Name</label>
<input id="name" name="name" autoComplete="name" required />
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" autoComplete="email" required />
<label htmlFor="message">Message</label>
<textarea id="message" name="message" rows={6} minLength={10} required />
<input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
{error ? <p role="alert">{error}</p> : null}
<button>Send</button>
</form>
);
}Content-Type: application/jsonは Ikiform に JSON として解析させ、Accept: application/jsonは JSON での応答を求めます。VITE_CONTEXTは名前の一例です。Netlify はビルド中にCONTEXTを設定するので、ビルドコマンドで Vite に渡すか、このフィールドを省いてください。- ハニーポットは JSON のキーとして送ります。入力欄が非表示でもボディに含めておけば、入力したボットは引き続き自らスパムだと名乗ることになります。
デプロイプレビューには専用のフォームを
デプロイプレビューは deploy-preview-12--your-site.netlify.app のようなホストに置かれます。これは your-site.netlify.app のサブドメインではないので Allowed domains で一致せず、プレビューからの送信はスパムになります。netlify.toml でプレビューとブランチデプロイに専用のフォームを割り当て、同じファイルで Content-Security-Policy ヘッダーを設定してください:
[build]
command = "npm run build"
publish = "dist"
[context.production.environment]
VITE_IKIFORM_FORM_ID = "frm_xxxxxxxxxxxxxxxx"
[context.deploy-preview.environment]
VITE_IKIFORM_FORM_ID = "frm_yyyyyyyyyyyyyyyy"
[context.branch-deploy.environment]
VITE_IKIFORM_FORM_ID = "frm_yyyyyyyyyyyyyyyy"
[[headers]]
for = "/*"
[headers.values]
Content-Security-Policy = "default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com"プレビュー用フォームの Allowed domains は空のままにするか、テスト中だけ正確なプレビューのホストを追加します。本番用フォームには自分のドメインと your-site.netlify.app を設定します。netlify.toml のヘッダーはすべてのデプロイコンテキストに適用されます。_headers ファイルを使う場合は、公開ディレクトリに含まれるようにしてください。
netlify.app だけを追加してはいけません。他人のサイトにあるあなたのフォームのコピーも含め、すべての Netlify サイトが一致してしまいます。動作を確認する
- デプロイプレビューを開いて送信し、メッセージがプレビュー用フォームに届くことを確認します。
- マージしてから本番で送信します。本番用フォームに届き、Netlify の Forms ページに新しいものが表示されないことを確認します。
- 通知の受信者それぞれの受信トレイを確認します。各自にフィールド入りのメールが届き、フォームに
emailフィールドがあれば Reply の宛先は送信者になります。 - ハニーポットを埋めて
_gotcha=botで送信します。成功の応答は返りますが、送信データはスパムとして保存され、メールも Webhook も送られません。 - 何も届かない場合は、ダッシュボードのステータスフィルターを Spam に切り替えてください。スパムにも本物の送信と同じ成功の応答が返ります。
ハマりどころ
| 問題 | 対処 |
|---|---|
送信すると 404 になる | 送信が自分のサイトに届いています。キャッシュされたビルドに古い action が残っているか、fetch の呼び出しがまだ / に送信しています。公開ディレクトリで form-name を検索してください。 |
| 送信データがまだ Netlify に表示される | ページ上のいずれかのフォームが data-netlify か netlify 付きで自分のサイトに送信しているため、Netlify が検出と保存を続けています。属性を削除し、action を Ikiform に設定して再デプロイしてください。 |
プレビューでだけ Ikiform から 404 not_found が返る | デプロイプレビューのコンテキストに VITE_IKIFORM_FORM_ID がないため、URL の末尾が undefined になっています。[context.deploy-preview.environment] に追加してください。 |
403 origin_not_allowed が返るか、すべての送信がスパムになる | ページのドメインがフォームの Allowed domains にありません。ドメインと、ローカルのテスト用に localhost を追加してください。登録済みドメインのサブドメインは許可されます。 |
422 files_not_supported | フォームの <input type="file"> でファイルが選択されています。ファイルアップロードにはまだ対応していません。入力欄を削除するか、代わりにリンクを尋ねてください。 |
よくある質問
はい。Netlify が処理するのはビルド時に検出したフォームだけです。data-netlify がなく、action が別のエンドポイントを指すフォームは、他のページと同じく Netlify が配信し、そのエンドポイントに直接送信されます。
Netlify は、デプロイされた HTML で Netlify Forms がそのフォームを検出していない限り、静的ページへの POST に 404 を返します。フォームに Netlify Forms 用の印を付けるか、action を別のバックエンドに向けてください。
はい。検出は属性によってフォームごとに行われます。Netlify に処理させたいフォームには data-netlify を残し、form detection はオンのままにしてください。
Netlify に残り、Ikiform には移りません。form detection をオフにする前にサイトの Forms ページからダウンロードし、記録として保管してください。
まだできません。ファイルを含む送信は 422 files_not_supported で拒否されます。代わりに、共有ドライブの URL などファイルへのリンクを尋ねてください。
手順で詰まったときや、あなたの状況に合うガイドがほしいときはメールでどうぞ: hello@ikiform.com