JavaScriptのfetch()を使ってHTMLフォームデータを送信する方法

HTML form submit fetch javascript tutorial showing code editor with fetch API call and form submission flow

コンタクトフォームを作成して、ユーザーが「送信」をクリックした後にページ全体がリロードされる様子を見たことがあるなら、その体験がいかに不快であるかご存知でしょう。HTML フォーム送信 fetch JavaScript のテクニックを使用することで、フォームデータをバックグラウンドでバックエンドエンドポイントに送信し、ユーザーを同じページに留めて、ページ全体の更新なしにクリーンな成功またはエラーメッセージを表示できます。このチュートリアルでは、基本的なHTMLの記述から、fetch()呼び出しをSendformエンドポイントに向けて送信内容を受信箱や連携ワークフローに直接届ける方法まで、すべてのステップを説明します。

重要なポイント:

  • Fetch APIを使用すると、ページリロードなしでフォームデータを送信でき、ユーザーにスムーズな体験を提供できます。
  • submitイベントをキャプチャしてpreventDefault()を呼び出すことが、Ajax フォーム送信パターンの基礎です。
  • Sendformは既製のエンドポイントURLを提供するため、送信を受信・保存するためのバックエンドコードは一切不要です。
  • 適切なレスポンス処理(成功またはエラーフィードバックの表示)は、データを正しく送信することと同じくらい重要です。

デフォルトのフォーム送信の代わりにfetch()を使う理由

ブラウザのデフォルトのフォーム送信動作は、フォームフィールドをシリアライズし、actionURLにPOST(またはGET)リクエストを送信し、サーバーが返すレスポンスをロードするという一つの処理を実行します。これは、ユーザーが白い画面のフラッシュを見て、スクロール位置を失い、新しいページが描画されるまで待つことを意味します。接続が遅い場合、これは壊れているように感じられます。

Fetch APIは、ページから離れることなく、完全にJavaScriptでHTTPリクエストをプログラム的に作成することでこの問題を解決します。これにより、ページリロードなしのフォーム送信パターンが可能になり、ユーザーのエンゲージメントを維持し、ローディング状態、インライン検証フィードバック、アニメーション付き成功バナーを含むユーザーエクスペリエンスのすべての側面を制御できます。

fetch()を好む実用的な理由:

  • プレーンなHTMLフォームでは送信できないカスタムヘッダー(CSRFトークンや認証キーなど)を添付できます。
  • エンドポイントが期待する形式に応じて、データをJSON、FormData、またはURL エンコード文字列としてシリアライズできます。
  • エラーハンドリングが明示的です。「失敗」が何を意味するかと、それをどのように伝えるかを決めることができます。
  • ロジックが完全にブラウザ内にあるため、GitHub Pages、Netlify、CDNでホストされているものも含む、あらゆる静的サイトで動作します。

注意: Webflow、WordPress、Hugoなどのウェブサイトビルダーを使用している場合も、同じfetch()アプローチが適用されます。プラットフォーム固有のヒントについては、ウェブサイトビルダーにSendformを統合する方法のガイドをご覧ください。

基本的なHTMLフォームの設定

JavaScriptを一行も書く前に、適切に構造化されたHTMLフォームが必要です。ここでの重要な詳細は、JavaScriptが送信を処理するため、どこかを指すaction属性は必要ないということです。ただし、すべての入力に意味のあるname属性が必要です。これらが送信されるペイロードのフィールドキーになります。

<form id="contact-form" novalidate>
  <div>
    <label for="name">お名前</label>
    <input type="text" id="name" name="name" required placeholder="山田太郎">
  </div>

  <div>
    <label for="email">メールアドレス</label>
    <input type="email" id="email" name="email" required placeholder="[email protected]">
  </div>

  <div>
    <label for="message">メッセージ</label>
    <textarea id="message" name="message" rows="5" required></textarea>
  </div>

  <button type="submit">メッセージを送信</button>

  <!-- フィードバック領域 -->
  <div id="form-feedback" aria-live="polite"></div>
</form>

このマークアップで注目すべき点:

  • form要素のnovalidateは、ネイティブブラウザ検証バブルを無効にし、JavaScriptでエラーメッセージを完全に制御できます。
  • aria-live="polite"を持つid="form-feedback"のdivは、成功およびエラーメッセージが表示される場所です。ARIA属性により、スクリーンリーダーが自動的にフィードバックを通知します。
  • すべての入力にid(ラベル関連付け用)とname(フォームペイロード用)の両方があります。

送信イベントのキャプチャ

あらゆるJavaScript フォーム送信の最初のステップは、ブラウザのデフォルト動作を阻止することです。これは、form要素のsubmitイベントをリッスンし、すぐにevent.preventDefault()を呼び出すことで行います。

const form = document.getElementById('contact-form');

form.addEventListener('submit', async function (event) {
  event.preventDefault(); // デフォルトのページナビゲーションを停止

  // 基本的なクライアントサイド検証
  const name = form.elements['name'].value.trim();
  const email = form.elements['email'].value.trim();
  const message = form.elements['message'].value.trim();

  if (!name || !email || !message) {
    showFeedback('すべてのフィールドを入力してください。', 'error');
    return;
  }

  // データの送信に進む(次のセクション)
  await submitForm({ name, email, message });
});

検証ロジックをネットワーク呼び出しから分離することで、コードを読みやすく拡張しやすく保てます。イベントハンドラーのasyncキーワードにより、内部でawaitを使用でき、Fetch API フォームデータ呼び出しが同期的に見え、深くネストしたPromiseチェーンを避けられます。

fetch()をSendformエンドポイントに向ける

ここが実際の作業です。フォーム送信を受信、保存、転送する独自のサーバーを構築する代わりに、Sendformをバックエンドとして使用できます。Sendformダッシュボードでフォームを作成すると、一意のエンドポイントURLが取得できます。そのURLがあれば十分です。

以下の送信関数は、FormData APIを使用してペイロードを構築し、Sendformがネイティブに受け入れます:

async function submitForm(data) {
  // このURLを実際のSendformエンドポイントに置き換えてください
  const SENDFORM_ENDPOINT = 'https://sendform.net/ja/YOUR_FORM_ID';

  const formData = new FormData();
  formData.append('name', data.name);
  formData.append('email', data.email);
  formData.append('message', data.message);

  try {
    const response = await fetch(SENDFORM_ENDPOINT, {
      method: 'POST',
      body: formData,
    });

    if (response.ok) {
      showFeedback('ありがとうございます!メッセージが送信されました。', 'success');
      form.reset();
    } else {
      const errorData = await response.json().catch(() => ({}));
      const errorMsg = errorData.message || '何かが間違っています。もう一度お試しください。';
      showFeedback(errorMsg, 'error');
    }
  } catch (networkError) {
    showFeedback('ネットワークエラーです。接続を確認してもう一度お試しください。', 'error');
  }
}

このコードで行われた重要な判断:

  • Content-Typeヘッダーは手動で設定されません。FormDataオブジェクトをbodyとして渡すと、ブラウザが正しいmultipart/form-data境界を自動的に設定します。
  • response.okチェックは、200だけでなくすべての2xxHTTPステータスコードをカバーします。これはresponse.status === 200を比較するよりも堅牢です。
  • 外側のtry/catchは、response.okでは決して見ることのないネットワークレベルの失敗(DNSエラー、オフライン状態)をキャッチします。
SendformエンドポイントURLを指すJavaScript fetch フォーム送信フロー

レスポンスの処理 - 成功およびエラーメッセージ

データの送信は作業の半分に過ぎません。ユーザーには即座に明確なフィードバックが必要です。上記で参照されたshowFeedback()ヘルパー関数は、HTMLに追加したフィードバックdivにメッセージを書き込みます:

function showFeedback(message, type) {
  const feedbackEl = document.getElementById('form-feedback');
  feedbackEl.textContent = message;
  feedbackEl.className = type === 'success' ? 'feedback-success' : 'feedback-error';
}

これは意図的に最小限です。実際のプロジェクトでは、textContentをアニメーションコンポーネントやトースト通知ライブラリに置き換えるかもしれませんが、パターンは同じです:fetch()呼び出しの結果に基づいてDOMを更新します。

送信後のカスタム感謝ページへのリダイレクトや下流自動化のトリガーなど、より高度なシナリオについては、Webhook、Zapier、APIを使用してフォームワークフローを自動化する方法の記事をご確認ください。

ベストプラクティスとコツ

コードを動作させることと、本番環境で耐えられる方法で出荷することは別のことです。覚えておくべき最も重要なヒントは以下の通りです:

  • リクエスト中は送信ボタンを無効にする。 fetch()を呼び出す前にbutton.disabled = trueを設定し、finallyブロックで再有効化します。これにより、ユーザーが複数回クリックした場合の重複送信を防げます。
  • ローディング状態を表示する。 リクエストが実行中の間、ボタンのテキストを「送信中...」に変更したり、スピナークラスを追加します。フィードバックが見えないユーザーは、何も起こらなかったと思い込んで再度クリックすることがよくあります。
  • サーバーサイドでも検証する。 クライアントサイド検証はユーザーエクスペリエンスのためです。Sendformやあらゆるバックエンドサービスは、すべての受信データを信頼できないものとして扱うべきです。
  • すべてでHTTPSを使用する。 プレーンHTTPでフォームデータを送信すると、ユーザー入力が転送中に露出します。SendformエンドポイントはデフォルトでHTTPSですが、自分のページもHTTPS経由で提供されることを確認してください。
  • スパム保護を追加する。 ハニーポットフィールドやCAPTCHA統合により、迷惑送信を大幅に削減できます。このトピックの詳細については、フォームのスパム保護ベストプラクティスのガイドをご覧ください。
  • エラーパスを意図的にテストする。 一時的にエンドポイントURLを無効なものに変更し、エラーメッセージが表示されることを確認します。ほとんどの開発者はハッピーパスのみをテストします。
  • エンドポイントURLをバージョン管理から除外する。 プロジェクトがオープンソースの場合、SendformエンドポイントをEnvironment変数や.gitignoreにリストされた設定ファイルに保存します。

静的サイトユーザー: プロジェクトがHugo、Eleventy、またはサーバーのないプレーンHTMLサイトの場合、ここで説明されているfetch()アプローチが推奨方法です。詳細は静的サイト向けサーバーレスフォーム処理の究極ガイドをお読みください。

まとめ

デフォルトのフォーム送信をfetch()呼び出しに置き換えることは、あらゆるコンタクトフォームやリード獲得フォームに対して行える最も影響の大きい改善の一つです。結果として、ユーザーをページに留めて、フィードバックメッセージを完全に制御できる、より速く、よりプロフェッショナルな体験が得られます。これをSendformエンドポイントと組み合わせることで、サーバーサイドコードの必要性を完全に排除できます。フォームが稼働し、送信が受信箱に届き、ユーザーは不快なページリロードを見ることがありません。今すぐ無料のSendformエンドポイントを作成して、数分で最初の送信を受信しましょう。

よくある質問

はい。fetch()は完全にブラウザで実行されるため、静的HTMLサイト、JAMstackプロジェクト、HTMLを提供するあらゆるプラットフォームで動作します。独自のサーバーは必要ありません。唯一の要件は、POSTリクエストを受信できるエンドポイント(SendformのURLなど)です。

fetch()XMLHttpRequestの現代的な代替品です。Promiseを使用し、async/awaitをサポートし、よりクリーンなAPIを持っています。新しいプロジェクトでは、fetch()が常に推奨されます。両方とも同じAjax フォーム送信結果を達成しますが、fetch()は大幅に少ないボイラープレートコードで済みます。

いいえ。FormDataオブジェクトをbodyとして渡すと、ブラウザが自動的にContent-Typemultipart/form-dataに設定し、正しい境界文字列を含めます。手動で設定すると、その境界値を省略することでリクエストが実際に壊れてしまいます。

Sendformにサインアップし、ダッシュボードで新しいフォームを作成し、生成されたエンドポイントURLをコピーします。そのURLをfetch()呼び出しのターゲットとして貼り付けます。送信は設定されたメールアドレスに即座に転送されます。

ネットワーク障害によりfetch() Promiseが拒否され、これはサンプルコードの外側のtry/catchブロックでキャッチされます。ユーザーには定義した「ネットワークエラー」メッセージが表示されます。送信は自動的にキューに入れられません。接続が復旧したら、ユーザーが再度試行する必要があります。