WordPress 2026.05.27

WordPressの予約フォーム二重送信を確実に防ぐ実装方法

約6分で読めます

予約フォームの二重送信は機会損失と業務混乱を招く深刻な問題です。原因から実践的な解決策まで、コード例を交えて徹底解説します。

こんな悩みを抱えていませんか?

「同じお客さんから予約が2件届いている」「キャンセル対応に追われて本業に集中できない」「二重予約でお客様に謝罪する羽目になった」——WordPressで予約フォームを運営しているWeb担当者から、こうした相談を受けることは珍しくありません。

特に飲食店・サロン・クリニックなど、予約管理が業務の根幹を担う業種では、二重送信による混乱は単なるシステムトラブルにとどまらず、顧客の信頼を損なう深刻な問題です。「送信ボタンを2回押してしまった」「ページをリロードしたら再送された」という原因がほとんどですが、見落としがちなケースもあります。

この記事では、二重送信が起きるメカニズムを正確に理解したうえで、JavaScriptによるフロントエンド対策とPHP側のバックエンド対策の両面から、確実に防ぐ実装方法を解説します。「とりあえずボタンをdisabledにすれば大丈夫」と思っていた方も、ぜひ最後まで読んでください。想定外の落とし穴があります。


なぜ二重送信が起きるのか?原因を正確に把握する

二重送信の原因は、大きく3つのパターンに分類できます。それぞれ対策が異なるため、「どのパターンで起きているか」を見極めることが重要です。

パターン① ユーザーが送信ボタンを連打する

最も一般的な原因です。フォーム送信後に画面の変化が乏しいと、ユーザーは「送信できたのか?」と不安になりボタンを再度クリックします。スマートフォンでのタップ操作時はとくに発生しやすい傾向があります。

パターン② ブラウザの「戻るボタン」や「リロード」

フォーム送信後に確認ページへ遷移した際、ユーザーがブラウザの戻るボタンを押してリロードすると、前回のPOSTリクエストが再実行されます。これは**PRGパターン(Post/Redirect/Get)**を実装していないサイトで頻発します。

パターン③ ネットワーク遅延によるタイムアウト再送

通信環境が不安定な場合、HTTPクライアントがリクエストの応答を待ちきれず再送することがあります。フロントエンド対策だけでは防ぎきれないため、サーバーサイドでの冪等性(べきとうせい)担保が不可欠です。


WordPressでお困りですか?

サイト制作・リニューアル・トラブル対応、プロにお任せください

無料で相談する

実践的な解決手順:フロントエンドとバックエンドの二重防衛

あるクライアントの事例をご紹介します。神奈川県内のネイルサロンで、Contact Form 7を使った予約フォームを運営していたのですが、月に3〜4件の二重予約が発生しており、スタッフが手作業で確認・キャンセル処理をしていました。対応工数は月あたり約2時間。「たった2時間」と思われるかもしれませんが、それ以上にお客様への謝罪連絡という心理的コストが大きいとオーナーは語っていました。

この案件では、フロントエンドとバックエンドの両方に対策を実装し、以降の二重送信をゼロに抑えることができました。

STEP 1:JavaScriptで送信ボタンを即時無効化する

まず最初に実施すべきは、送信ボタンをクリックした瞬間にdisabled状態にしてユーザーの連打を防ぐことです。WordPressのテーマ内に配置するカスタムJavaScriptとして実装します。

document.addEventListener('DOMContentLoaded', function () {
  const forms = document.querySelectorAll('form.reservation-form');

  forms.forEach(function (form) {
    form.addEventListener('submit', function (e) {
      const submitBtn = form.querySelector('button[type="submit"], input[type="submit"]');

      if (submitBtn.dataset.submitting === 'true') {
        // すでに送信中の場合はイベントをキャンセル
        e.preventDefault();
        return;
      }

      submitBtn.dataset.submitting = 'true';
      submitBtn.disabled = true;
      submitBtn.textContent = '送信中...';
    });
  });
});

注意点: disabled にするだけでは不十分なケースがあります。JavaScriptが無効な環境や、フォームプラグインが独自のサブミット処理をしている場合はスキップされることがあります。あくまで「ユーザー体験の改善」と捉え、バックエンド対策と必ずセットで使いましょう。

STEP 2:トークンによるサーバーサイドの重複チェック

フロントエンドだけに頼るのは危険です。ブラウザの戻るボタンやネットワーク再送には無力であるため、ワンタイムトークン(Nonce) を使ったサーバーサイドの検証が必須です。

WordPressには標準でwp_nonceという仕組みが用意されていますが、これはCSRF対策であり二重送信防止には不向きです。セッションを活用したカスタム実装が効果的です。

<?php
// フォーム表示時にユニークなトークンを生成してセッションに保存
function generate_form_token(): string {
    if (session_status() === PHP_SESSION_NONE) {
        session_start();
    }
    $token = bin2hex(random_bytes(16));
    $_SESSION['form_token'] = $token;
    $_SESSION['form_token_time'] = time();
    return $token;
}
?>
<form method="post" class="reservation-form">
    <input type="hidden" name="form_token"
           value="<?php echo esc_attr(generate_form_token()); ?>">
    <!-- フォームフィールド -->
    <button type="submit">予約を送信する</button>
</form>

STEP 3:PRGパターンでリロード再送を防ぐ

送信処理が完了したら、wp_redirect()を使って別のURLへリダイレクトさせます。これにより、ユーザーが完了ページをリロードしても再送信が起きなくなります。

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && validate_form_token()) {
    // 予約データをデータベースへ保存
    save_reservation_data($_POST);

    // PRGパターン:完了ページへリダイレクト(Getリクエストに変換)
    wp_redirect(home_url('/reservation-complete/'));
    exit;
}

この3ステップを組み合わせることで、連打・ブラウザ操作・ネットワーク再送のすべてに対応できます。


よくある失敗パターンと対処法

失敗① nonceだけ実装して「対策済み」と思い込む

WordPressのドキュメントを見てwp_nonce_field()check_admin_referer()を実装した方は多いですが、nonceは1時間有効であり、同一nonceで複数回の送信が通ります。CSRF対策にはなっても二重送信防止にはなりません。前述のセッショントークン方式と組み合わせて使うのが正解です。

失敗② disabled処理を送信後に行う(タイミングのズレ)

// ❌ 悪い例:フォームのsubmitイベントの「後」に処理
submitBtn.addEventListener('click', function() {
  setTimeout(() => { this.disabled = true; }, 100);
});

setTimeoutを使ったり、非同期処理の完了後にdisabledを設定するコードを見かけることがありますが、送信イベントが発火してからボタンが無効化されるまでの間に連打できてしまいます。submitイベントのハンドラー内で同期的に処理する必要があります。

失敗③ キャッシュプラグインがセッションを無効化している

W3 Total CacheやWP Super Cacheなどのキャッシュプラグインを使っている環境では、フォームページがキャッシュされてトークンが再生成されないケースがあります。フォームページはキャッシュ除外設定を必ず入れてください。

// wp-config.php またはフォームページのテンプレートに追記
define('DONOTCACHEPAGE', true);

WordPressでお困りですか?

サイト制作・リニューアル・トラブル対応、プロにお任せください

無料で相談する

サイトの改善、プロに任せませんか?

WordPress制作・リニューアル

更新しやすく、集客につながるサイトを構築します

200件以上の制作実績 顧客満足度97% 初回相談無料

※ 通常1営業日以内にご返信します

まとめと次のステップ

二重送信対策は「ボタンをdisabledにする」だけでは不完全です。フロントエンドでのUI制御、サーバーサイドでのトークン検証、そしてPRGパターンによるリロード対策——この三層構造で初めて堅牢な実装が完成します。

今回紹介したコードは実案件で実際に使っているものですが、フォームプラグインの種類・テーマの構成・サーバー環境によって実装方法が変わることもあります。「自分の環境に合わせてカスタマイズできるか不安」という場合は、お気軽にご相談ください。

予約フォームの品質は、そのままビジネスの信頼性に直結します。対策を後回しにするほど、顧客への謝罪対応というコストが積み重なっていきます。Fivenine Designでは、WordPressのフォーム実装から既存サイトの改修まで幅広く対応しています。まずは現状の課題をお聞かせください。

この記事をシェア

WordPressサイト、もっと良くできます

制作・リニューアル・表示速度改善まで、WordPressのお悩みを解決します。 初回相談は無料です。

※ 1営業日以内にご返信いたします

この技術でお困りなら

無料でプロに相談できます

相談する
AIに無料相談