JOURNAL

フォームのバリデーション、リアルタイムかsubmit時か問題

Webサイトのお問い合わせや会員登録フォームを作るとき、必ず議論になるのが「入力チェック(バリデーション)はどのタイミングで行うべきか?」という問題です。

「入力しているその場で教えてあげるのが一番親切でしょ!」と単純にリアルタイム判定を入れると、かえってユーザーをイラつかせる地獄のフォームが完成します。今回は、バリデーションの適切なタイミング設計と、実戦で使えるスマートな実装パターンについてまとめます。

1. 初心者がやりがちな「せっかちバリデーション」の惨劇

リアルタイムチェックで一番やってはいけないのが、文字を1文字打つたび(inputイベント)にエラーを判定して赤い警告を出す実装です。

😡 ユーザーが一番イラッとする瞬間

メールアドレス欄に t と1文字打った瞬間に、
「❌ メールアドレスの形式が正しくありません!」
と真っ赤な字で怒られる。「今から打つとこだからちょっと待ってよ!」と叫びたくなりますよね😅

入力途中の「未完成な状態」をエラーとして断罪してしまうと、ユーザーは常にシステムから監視されて急かされているような不快感を覚えます。

2. 状況によって使い分ける「4つの検証タイミング」

バリデーションは「リアルタイムか送信時か」の二者択一ではなく、項目の性質に応じて以下の4つのタイミングを組み合わせるのが正解です。

  • ① 離脱時(blur):入力欄からフォーカスが外れた瞬間に判定。
    メールアドレスや電話番号など、「入力が終わった」とみなせるタイミングでチェックするため、入力中の邪魔をしません。
  • ② 入力中(input / リアルタイム):1文字打つごとに判定。
    「パスワードの強度ゲージ」「残り文字数カウンター」「重複チェック(要デバウンス)」など、入力しながら進捗を見たい項目に限定して使います。
  • ③ 修正中(再検証 / Re-validation):一度エラーが出た後の判定。
    エラーが出た項目をユーザーが修正している最中は、正しい形式になった瞬間にリアルタイムで赤エラーを消して緑チェック(成功)にするのが最大の親切です(一度怒られたら、直した瞬間にすぐ許してあげる優しさ)。
  • ④ 送信時(submit):送信ボタンが押されたときの最終判定。
    必須項目の未入力チェックや、複数項目の相関チェック(パスワード確認用との一致など)を一括で行い、最初のエラー箇所へ自動フォーカスを当てます。

3. 実装例:ストレスのないスマートな検証ロジック

「普段はフォーカスが外れたらチェックし、エラー発生後は入力中にリアルタイムで解除する」という理想的な挙動は、フラグ管理を使えばシンプルに書けます。

const emailInput = document.getElementById('email');
const errorMsg = document.getElementById('email-error');
let hasBlurred = false; // 一度でもフォーカスが外れたか

function validateEmail() {
  const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailInput.value);
  if (!isValid) {
    errorMsg.textContent = 'メールアドレスの形式が正しくありません(例: user@example.com)';
    emailInput.setAttribute('aria-invalid', 'true');
  } else {
    errorMsg.textContent = '';
    emailInput.removeAttribute('aria-invalid');
  }
}

// 1. 初回はフォーカスが外れた瞬間に判定
emailInput.addEventListener('blur', () => {
  hasBlurred = true;
  validateEmail();
});

// 2. 一度エラーが出た後は、入力中にリアルタイムで解決を反映
emailInput.addEventListener('input', () => {
  if (hasBlurred) {
    validateEmail();
  }
});
検証タイミング向いている項目・用途ユーザー体験(UX)
入力中(input)文字数カウント、パスワード強度リアルタイムに進捗が分かって快適
離脱時(blur)メール、電話番号、郵便番号入力完了後に判定されるため邪魔にならない
修正中(再検証)一度エラーになった項目の入力直った瞬間にエラーが消えて安心できる
送信時(submit)必須未入力、パスワード再入力一致全体の最終確認とエラー箇所への誘導

4. 【超重要】JSのチェックはすり抜けられる! サーバー側(PHP/Python/Go等)が最後の砦

ここまでJavaScriptによる入力チェックの話をしてきましたが、ここでWeb開発における最も重要で絶対に忘れてはいけない鉄則を一つ。

「JavaScript側のバリデーションは、悪意があれば1秒ですり抜けられる」ということです。

⚠️ フロントエンドのチェックを過信してはいけない理由

  • ブラウザの開発者ツール(F12)でJavaScriptをオフにしたり、HTMLの required 属性を消して送信できる
  • そもそもブラウザを使わず、curl やスクリプト(Pythonなど)から直接サーバーのAPIエンドポイントへ不正データを送りつけることができる

つまり、メール送信システムやお問い合わせフォームにおいて、JavaScriptのチェックだけで安心してしまうのは「鍵のかかっていない自動ドア」で防犯しているようなものです。

そのため、本番システムでは必ず以下の「二重構造」で設計します。

  • JavaScript(フロントエンド):「使いやすさ(UX)」のための案内係。
    入力ミスをその場で素早く教えて、ユーザーの手間を減らすための親切機能。
  • サーバー側バックエンド(PHP, Python, Go, Node.js, Rust, Java等):「セキュリティとデータ保護」のための鉄壁の門番。
    届いたデータは一切信用せず(Never Trust User Input)、メール形式や文字数、不正文字(XSS・SQLインジェクション対策)を必ず厳格に再検証する。
検証場所主な目的・役割すり抜け耐性位置づけ
JavaScript(ブラウザ)ユーザー体験(UX)の向上・即時案内✕(簡単に回避可能)親切な「案内係」
サーバー側(PHP / Python / Go / Node.js 等)セキュリティ・不正データ遮断・DB保護◎(回避不可能)鉄壁の「最後の砦」

結論:バリデーションは「叱るため」ではなく「助けるため」にある

バリデーションの目的は、間違ったユーザーを摘発して叱ることではなく、「迷わずスムーズにゴール(送信)まで辿り着けるように道案内すること」です。

ブラウザ側(JS)ではユーザーを優しくエスコートし、サーバー側(PHPやGo、Pythonなど)では裏でどっしりと城門を守る。この「優しさと厳格さの使い分け」こそが、安全で使い心地の良いWebサイトを作る秘訣です。