静的サイトのお問い合わせフォーム2 分で読めます
Vercel でホストしたお問い合わせフォームの 405 エラーを直す
Vercel の静的ページはフォームの POST に 405 Method Not Allowed を返します。その理由と、代わりにバックエンドへフォームを送る方法を説明します。
Vercel は静的ビルドをファイルとして配信するので、ファイルへの POST は、そのパスを処理する関数がないため空のボディの 405 になります。フォームの action を Ikiform のエンドポイントに向けるか、送信を受け付ける Vercel Function を書いてください。Ikiform を使う場合は、Vercel の環境ごとにフォーム ID を設定し、プレビューはテスト用フォームに、本番は本物のフォームに送信されるようにします。
Vercel が 405 を返す理由
Vercel は各リクエストを静的ファイルか関数に振り分けます。静的サイト、Vite のビルド、output: "export" の Next.js アプリにはファイルしかありません。/contact への GET はファイルを返しますが、同じパスへの POST には行き先がないので、あなたのコードが動く前に Vercel が拒否します:
curl -i -X POST -d "message=hello" https://your-project.vercel.app/contactHTTP/2 405
server: Vercel
content-disposition: inline
x-vercel-cache: HIT
content-length: 0応答にボディがないので、ブラウザーには空白のページか独自のエラー画面が表示されます。原因になる構成は 3 つあります:
| あなたのコード | Vercel で 405 になる理由 |
|---|---|
action のない <form method="post"> | フォームが置かれているページ、つまり静的ファイルに送信している |
Vite やプレーンな HTML のプロジェクトでの action="/api/contact" | プロジェクトのルートの api/ フォルダーにファイルがないので、そのパスの関数が存在しない |
| 静的エクスポートでの Next.js の Server Action や Route Handler | output: "export" は Server Actions も、リクエストを読む Route Handler もサポートしない。next dev はエラーを報告し、ビルドにはそれらを動かすサーバーがない |
- フォームが Vercel のデプロイ上のパスに送信します。
- Vercel はそのパスを処理する関数を探します。
- 関数を書いていればそれが実行されます。静的ビルドには関数がないので、Vercel は空のボディで 405 を返します。
- action が Ikiform を指していれば、送信は Vercel に届きません。
- Ikiform がメッセージを保存してメールで通知し、ブラウザーを Vercel サイト上の静的ページにリダイレクトします。
つまり、関数を追加するか、送信を別の場所に送るかのどちらかです。api/contact.ts の Vercel Function でも動きますが、メール API、スパム除外、レート制限が必要になり、それを自分で保守することになります。送信を Ikiform に送れば、デプロイは静的なままです。HTML の修正は属性 1 つだけです:
<form method="post">
<input name="email" type="email" required />
<textarea name="message" required></textarea>
<button>Send</button>
</form><form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">関数を自分で書きたい場合
自分のコードしか届かない場所にメッセージを送る必要があるなら、関数は妥当な選択です。始める前に、必要なものを数えておきましょう:
- メールプロバイダーのアカウント、認証済みの送信ドメイン、Vercel の環境変数に入れた API キー。
- サーバー側のバリデーション。ブラウザーの
required属性は便利なだけで、チェックにはなりません。 - スパム除外とレート制限。なければ、インターネット中のボットからのメールが届くお問い合わせフォームになります。
- メールプロバイダーが停止しているときにメッセージを保管する場所と、再送する方法。
フォームバックエンドなら、この一覧をまとめて引き受けます。それでも自分のコードを挟みたいなら、フォームの送信先は Ikiform のままにして、関数で署名付き Webhook を受け取ってください。ハンドラーは自分のサーバーで紹介しています。
Vercel の環境ごとにフォーム ID を分ける
Vercel ではプルリクエストごとにプレビューのデプロイが作られます。プレビューが本番のフォームに送信すると、テストのメッセージがすべて本物のメッセージと並んで受信トレイに届きます。プレビュー用に 2 つ目の Ikiform フォームを作り、Vercel の環境変数で切り替えましょう。Settings › Environment Variables で、同じ名前を 2 回追加します:
VITE_IKIFORM_FORM_ID Production frm_xxxxxxxxxxxxxxxx
VITE_IKIFORM_FORM_ID Preview frm_yyyyyyyyyyyyyyyyVite はビルド時に index.html の %NAME% プレースホルダーを VITE_ 変数で置き換えるので、プレーンな HTML フォームでも JavaScript なしで使えます。VITE_VERCEL_ENV は Vite のプロジェクトで Vercel が設定する変数で、メッセージがどのデプロイから来たかを記録します:
<form id="contact" action="https://ikiform.com/f/%VITE_IKIFORM_FORM_ID%" method="post">
<label for="name">Name</label>
<input id="name" name="name" autocomplete="name" required />
<label for="email">Email</label>
<input id="email" name="email" type="email" autocomplete="email" required />
<label for="message">Message</label>
<textarea id="message" name="message" rows="6" minlength="10" required></textarea>
<input type="hidden" name="build" value="%VITE_VERCEL_ENV%" />
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
<button type="submit">Send message</button>
</form>- 静的サイトでは、Vercel はビルド時に環境変数を読みます。変更したら再デプロイしてください。そうしないと HTML に古い値が残ります。
- 変数が存在しないとき、Vite はプレースホルダーをそのまま残すので、ID がないと
%VITE_IKIFORM_FORM_ID%のまま公開され、すべての送信がフォームに届く前に400 Bad Requestで失敗します。下のガードでそれを検出します。 - フォーム ID はどちらにせよ公開されます。ブラウザーに届くのは
VITE_変数だけなので、そこにシークレットを入れてはいけません。
const form = document.querySelector("#contact");
if (form && form.action.includes("%VITE_")) {
console.error("VITE_IKIFORM_FORM_ID is not set for this Vercel environment");
form.querySelector("button").disabled = true;
}output: "export" の Next.js を使っていますか?その場合はクライアントコンポーネントで process.env.NEXT_PUBLIC_IKIFORM_FORM_ID を読んでください。静的エクスポート用のフォームはメインガイドにあります。Next.js アプリが Vercel のサーバーで動いているなら、Server Action からシークレットキーで送信できます。Next.js ガイドを参照してください。
ヘッダーとクリーン URL
サイトで Content-Security-Policy を設定しているなら、vercel.json で設定して Ikiform を含めてください。cleanUrls を使うと thanks.html が /thanks で配信され、リダイレクト先がすっきりします:
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"cleanUrls": true,
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com"
}
]
}
]
}プレビューと許可ドメイン
your-project-git-branch-team.vercel.app のようなプレビューの URL は your-project.vercel.app のサブドメインではないので、Allowed domains で一致しません。そこからの送信は origin_mismatch でスパムになります。これもプレビュー用のフォームを分ける理由の 1 つです。プレビュー用フォームの許可ドメインは空のままにするか、テストするプレビューのホストを追加してください。
localhost のテストではないので、本番の送信データと同じくプランの使用量にカウントされます。動作を確認する
- 修正前に本番の URL に対して上の
curlを実行し、405を確認します。修正後は、デプロイされた HTML のフォームのactionがhttps://ikiform.com/f/で始まります。 - プレビューのデプロイを開いて送信し、メッセージが本番ではなくプレビュー用のフォームに届くことを確認します。
- 本番のドメインで送信し、
buildフィールドがproductionになっていることを確認します。 - 通知の受信者それぞれの受信トレイを確認します。各自にフィールド入りのメールが届き、フォームに
emailフィールドがあれば Reply の宛先は送信者になります。 - 何も届かない場合は、ダッシュボードのステータスフィルターを Spam に切り替えてください。スパムにも本物の送信と同じ成功の応答が返ります。
ハマりどころ
| 問題 | 対処 |
|---|---|
フォームを変更しても 405 のまま | テストしているデプロイが変更前にビルドされたものか、JavaScript のハンドラーが fetch で自分のパスに送信しています。ビルドされたファイルで /api/contact を検索してください。 |
すべての送信が 400 Bad Request になる、または Ikiform から 404 not_found が返る | フォーム ID がビルドに届いていないか、その環境用の ID が間違っています。その環境に変数があることを確認して、再デプロイしてください。 |
| プレビューは動くのに、本番の送信がスパムになる | 本番用フォームの Allowed domains に本番のドメインがありません。your-project.vercel.app に加えて独自ドメインも追加してください。 |
| 自分のページではなく Ikiform のサンクスページに移動する | _redirect は、ドメインが Allowed domains にあり、かつ https: の URL でない限り無視されます。ドメインを追加するか、代わりにフォーム設定で Redirect after submit を設定してください。 |
よくある質問
フォームが静的ファイルしかないパスに送信しているからです。Vercel は GET リクエストにはファイルを返しますが、そのパスに関数がなければ POST を 405 で拒否します。フォームの送信先をフォームバックエンドにするか、Vercel Function を追加してください。
使えません。output: export では、実行するサーバーがないため Server Actions はサポートされません。クライアントコンポーネントからフォームバックエンドに送信するか、静的エクスポートをやめてデプロイし、Vercel で Action を実行してください。
使わないほうがよいでしょう。Preview 環境の変数でプレビュー専用のフォーム ID を渡せば、テストのメッセージが受信トレイや Webhook に届きません。
不要です。フォームはなくても動きます。vercel.json を追加するのは、ikiform.com を許可する Content-Security-Policy を設定するときか、cleanUrls をオンにしてサンクスページの URL を整えたいときだけです。
いいえ、無料プランはありません。テストは無料で、支払いの前に localhost から月 50 件のテスト送信ができます。本番運用するには、月 $6 で 500 件からの Pro を選ぶか、1 回払いで 10,000 件 $49 または 50,000 件 $149 を購入できます。クレジットに有効期限はありません。
手順で詰まったときや、あなたの状況に合うガイドがほしいときはメールでどうぞ: hello@ikiform.com