予約フォームの二重送信はデータの重複や顧客トラブルを引き起こす深刻な問題です。原因から実装レベルの防止策まで、実案件の知見をもとに解説します。
こんな悩みはありませんか?
「予約フォームから同じ内容のメールが2通届いた」「顧客のデータベースに重複した予約が入っている」「送信ボタンを押した直後に画面が固まり、再度押したら二重になってしまった」——。
WordPressで予約フォームを運用しているWeb担当者や開発者から、こうした相談が定期的に寄せられます。二重送信は単なる「ちょっとした不具合」ではありません。重複した予約によってスタッフが混乱し、顧客対応に余計な工数が発生する。場合によっては予約枠の誤管理や、顧客への重複請求というトラブルに発展することもあります。
FivenineDesignでは20年以上にわたって神奈川を中心とした中小企業のWeb制作に携わってきましたが、予約フォームの二重送信は「設置して終わり」という運用の落とし穴としてもっとも頻繁に発見される問題のひとつです。この記事では、なぜ二重送信が起きるのかという根本原因から、実装レベルで確実に防ぐ方法まで、実案件の経験をもとに具体的に解説します。
なぜ二重送信が起きるのか?原因を正しく理解する
二重送信の原因は、大きく分けて3つのパターンに分類できます。根本原因を把握しておかないと、対策を講じても別のルートから同じ問題が再発するので注意が必要です。
① ユーザーの操作による二重押し(約45%)
もっとも多い原因です。送信ボタンを押した後、画面に変化がなく「送信できたか不安になってもう一度押す」というユーザー行動が引き金になります。特にスマートフォンでは画面の反応が分かりにくく、二度タップが起きやすい傾向があります。
② ネットワーク遅延・タイムアウトによる再送信(約35%)
回線が不安定な環境ではPOSTリクエストが完了する前にタイムアウトが発生し、ブラウザが自動的にリクエストを再送するケースがあります。サーバー側でリクエストを処理し終えた後に重複リクエストが届くこともあり、この場合はユーザーに非がないため検知が難しいです。
③ ブラウザの戻るボタン・ページリロード(約20%)
送信完了ページに遷移した後にブラウザバックし、再度フォームを送信するパターン。あるいは送信完了ページをリロードしたことで、ブラウザが「フォームデータを再送信しますか?」というダイアログを表示し、ユーザーが誤って確認してしまうケースです。
実装レベルの防止策:3つのアプローチ
二重送信を防ぐには、フロントエンド(JavaScript)、バックエンド(PHP/WordPress)、UX設計の3層で対策を重ねることが理想です。一つだけでは防ぎきれない穴が生まれるため、複数を組み合わせてください。
アプローチ1:送信ボタンを即座に無効化する(フロントエンド)
もっとも手軽で即効性があるのが、送信ボタンをクリックした瞬間に disabled にする方法です。Contact Form 7などのプラグインを利用している場合でも、以下のコードを functions.php か専用のJSファイルに追加することで対応できます。
document.addEventListener('DOMContentLoaded', function () {
const forms = document.querySelectorAll('form.wpcf7-form, form.reservation-form');
forms.forEach(function (form) {
form.addEventListener('submit', function (e) {
const submitBtn = form.querySelector('[type="submit"]');
if (!submitBtn) return;
// 既に送信中なら処理をブロック
if (submitBtn.dataset.submitting === 'true') {
e.preventDefault();
return;
}
submitBtn.dataset.submitting = 'true';
submitBtn.disabled = true;
submitBtn.textContent = '送信中...'; // ユーザーへのフィードバック
});
});
});
ポイントは disabled にするだけでなく、ボタンのテキストを「送信中...」に変えることです。ユーザーに「処理が進んでいる」と視覚的に伝えることで、焦りによる再クリックを防止できます。送信失敗時には disabled を解除してユーザーが再送信できる状態に戻すことも忘れないようにしてください。
アプローチ2:ワンタイムトークンで二重登録をサーバー側でブロック(バックエンド)
フロントエンドの対策はJavaScriptを無効化している環境やブラウザ拡張などで迂回されることがあります。本当に信頼できる防止策はサーバー側に実装する必要があります。
具体的には、フォームを表示するタイミングで一意のトークンを生成してhiddenフィールドに埋め込み、そのトークンが「1度しか使えない」仕組みにします。
<?php
// フォームを表示するときにトークンを生成
function fnd_generate_form_token(): string {
$token = bin2hex(random_bytes(32));
$used_tokens = get_transient('fnd_form_tokens') ?: [];
$used_tokens[$token] = time();
// 10分間有効なトークンとして保存
set_transient('fnd_form_tokens', $used_tokens, 600);
return $token;
}
$token = fnd_generate_form_token();
?>
<form method="post" action="">
<input type="hidden" name="form_token" value="<?php echo esc_attr($token); ?>">
<!-- フォームの各フィールド -->
<button type="submit">予約を確定する</button>
</form>
WordPressの Transients API を使うことで、データベースに状態を保存しながらも自動的に期限切れ処理が走るため、古いトークンが蓄積し続けることもありません。
アプローチ3:PRGパターンで「リロード二重送信」を防ぐ
POST送信後にそのまま完了画面を表示すると、ユーザーがページをリロードした際にブラウザが「フォームデータを再送信しますか?」と確認ダイアログを出します。これを防ぐのが PRGパターン(Post/Redirect/Get) です。
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// バリデーション・保存処理をここで実行
// ...
// 処理後は必ずGETリクエストのページへリダイレクト
wp_redirect(home_url('/reservation-complete/?ref=ok'));
exit;
}
完了ページのURLに ?ref=ok のようなパラメータを付けると、完了ページかどうかを判別しやすくなります。リロードしても GET リクエストになるため、フォームの再送信は発生しません。
よくある失敗パターンと対処法
対策を施したつもりでも、現場ではいくつかの落とし穴があります。実際に遭遇したケースをもとに紹介します。
実案件での事例:神奈川の整体院サイト
神奈川県内の整体院様から「予約が重複していて、当日お客様が2人同じ時間枠に来てしまった」というご相談をいただいたことがあります。調査してみると、Contact Form 7をそのまま使っており、サーバー側の重複防止処理が一切なかったことが原因でした。
対策として、フロントエンドのボタン無効化に加えて、予約日時と電話番号の組み合わせによる重複チェックをDBレベルで実装しました。同一の日時・顧客情報が既にデータベースに存在する場合は処理をスキップし、管理者に重複アラートメールを送る仕組みも追加。改修後はリリースから6ヶ月間で重複予約件数がゼロになり、スタッフの確認作業も週3時間分削減できた、とご報告をいただきました。
サイトの改善、プロに任せませんか?
WordPress制作・リニューアル
更新しやすく、集客につながるサイトを構築します
※ 通常1営業日以内にご返信します
まとめと次のステップ
予約フォームの二重送信は「運が悪ければ起きる」問題ではなく、対策を怠れば確実に起きる構造的な問題です。フロントエンド・バックエンド・UX設計の3層で防御することで、ほぼ完全に防止できます。
今すぐ確認すべき作業をチェックリストにまとめました。一つずつ対応してください。
「コードを見てもどこから手をつければいいか分からない」「自社のフォームに合った実装をしてほしい」という場合は、Fivenine Designまでお気軽にご相談ください。既存のWordPressサイトへの改修対応も承っております。実装内容と状況を確認したうえで、最適な方針をご提案します。