WithCodeMedia-1-pc
previous arrowprevious arrow
next arrownext arrow

WithCodeMedia-1-sp
previous arrowprevious arrow
next arrownext arrow

【実案件で使える】Jamstackフォーム処理 | サーバーレスでの実装方法を徹底解説

生徒

先生、静的サイトでお問い合わせフォームを実装したいんですけど、バックエンドサーバーがないとフォーム送信できませんよね…どうすればいいですか?

ペン博士

よーく聞くんだぞ。今日はサーバーレス関数を使ったフォーム処理について詳しく解説するぞい!静的サイトでも簡単にフォーム機能を実装できるんじゃ!

生徒

そう言う方法があるんですね!はい!お願いします!

Jamstackサイトは、高速でセキュアなWebサイトを構築できる一方で、動的な処理が必要なフォーム送信では課題が発生します。従来のサーバーサイド処理が使えないため、フォームデータの受信やメール送信を別の方法で実装する必要があります。

そこで活用するのが、サーバーレス関数(Serverless Functions)です。サーバーレス関数を使えば、サーバーを管理することなく、フォーム処理を実装できます。

本記事では、Jamstackサイトでのフォーム処理をサーバーレスで実装する方法を、複数のプラットフォーム(Netlify、Vercel、AWS Lambda)を使った実装例とともに詳しく解説します。

「学習→案件獲得」につなげた受講生のリアルな体験談も公開中!
働き方を変えたい方にも響くストーリーです。

菅井さん
将来的への不安と子育てという背景から「副業」に挑戦しようと決意。独学からプログラミングの学習を開始していたが、WithCodeに出会い体験コースを受講。約4ヶ月の学習に取り組み、当初の目標であった卒業テスト合格を実現した。WithCode Platinumにて3件の案件を担当し、現在は副業だけでなく本格的に「フリーランス」として在宅で活躍していきたいと考えるようになる。

詳しくはこちらの記事をご覧ください。

あわせて読みたい
【子育てママさん必見】「在宅×副業」を達成!多方向で活躍中のママさんフリーランスにインタビュー! ペン博士、ママさんなのに在宅で副業もこなしている方がいるって聞いたんですけど、本当ですか?だとしたらすごすぎます! うむ、うちの受講生でな、まさに“時間も場所...

菅井さんの主な制作実績はこちら


目次

Jamstackとは?

Jamstack(ジャムスタック)は、JavaScript、API、Markupの頭文字を取った、モダンなWeb開発アーキテクチャです。従来のサーバーサイドレンダリングではなく、事前にHTMLを生成(Pre-rendering)し、CDNで配信することで、高速で安全なWebサイトを実現します。

Jamstackの特徴

  • 高速なパフォーマンス:静的ファイルをCDNから配信するため、表示速度が速い
  • 高いセキュリティ:サーバーサイドの攻撃対象が少ない
  • スケーラビリティ:CDNで配信されるため、アクセス増加に強い
  • 低コスト:サーバー管理が不要で、ホスティング費用が安い

主要なJamstackプラットフォーム

  • Netlify:フォーム機能、Functions、デプロイが統合
  • Vercel:Next.jsとの相性が良い、Serverless Functionsを提供
  • Cloudflare Pages:高速なCDN、Workers機能
  • AWS Amplify:AWSエコシステムとの統合

Jamstackサイトのフォーム処理の課題

生徒

静的サイトだと、なぜフォーム処理が難しいんですか?

ペン博士

静的サイトにはバックエンドサーバーがないから、フォームデータを受け取る仕組みを別途用意する必要があるんじゃ!

従来のフォーム処理(サーバーサイド)

従来のWebサイトでは、PHPやNode.jsなどのサーバーサイド言語でフォームデータを受け取り、処理していました。

// 従来のPHPでのフォーム処理例
<?php
if ($_POST['name'] && $_POST['email']) {
  $name = $_POST['name'];
  $email = $_POST['email'];
  mail('admin@example.com', 'お問い合わせ', $message);
}
?>

しかし、Jamstackサイトは静的ファイルのみで構成されるため、このような処理ができません。

Jamstackでのフォーム処理の解決策

Jamstackサイトでフォーム処理を実現する方法は、主に以下の3つです。

  1. サーバーレス関数(Serverless Functions):自分でコードを書いて処理
  2. フォーム送信サービス:Netlify Forms、Formspreeなどの専用サービスを利用
  3. 外部API:SendGrid、Mailgun、Firebase Firestoreなどを利用

次では、サーバーレス関数を使った実装方法を中心に解説します。


サーバーレス関数とは?

サーバーレス関数(Serverless Functions)は、サーバーを管理せずに実行できる関数です。必要なときだけ実行され、使った分だけ課金される仕組みで、運用コストが低いのが特徴です。

サーバーレス関数の特徴

  • サーバー管理不要:インフラの設定や保守が不要
  • 自動スケーリング:アクセス数に応じて自動的に処理能力が調整される
  • 従量課金:実行した回数・時間に応じた課金
  • 高可用性:複数のリージョンで自動的に冗長化

主要なサーバーレス関数プラットフォーム

プラットフォームサービス名言語無料枠
NetlifyNetlify FunctionsJavaScript、TypeScript、Go125,000リクエスト/月
VercelServerless FunctionsJavaScript、TypeScript、Go、Python、Ruby100GB-時間/月
AWSLambdaNode.js、Python、Go、Java、C#など100万リクエスト/月
FirebaseCloud FunctionsJavaScript、TypeScript200万呼び出し/月

実装方法1:Netlify Functionsを使ったフォーム処理

生徒

Netlify Functionsってなんですか?

ペン博士

Netlify Functionsは、Netlifyが提供するサーバーレス関数サービスじゃ。デプロイが簡単で、初心者にも扱いやすいのが特徴じゃ!

ステップ1:プロジェクト構造の準備

my-jamstack-site/
├── public/
│   └── index.html
├── netlify/
│   └── functions/
│       └── form-handler.js
└── netlify.toml

ステップ2:フォームHTMLの作成

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>お問い合わせフォーム</title>

  <style>
    body {
      font-family: system-ui, sans-serif;
      line-height: 1.6;
    }

    h1 {
      margin-bottom: 24px;
    }

    #contact-form {
      display: flex;
      flex-direction: column;
      gap: 24px; 
      max-width: 500px;
    }

    /* 各フィールド */
    #contact-form > div {
      display: flex;
      flex-direction: column;
      gap: 8px; /* ラベルと入力欄の間隔 */
    }

    label {
      font-weight: 600;
    }

    input,
    textarea {
      padding: 12px;
      border: 1px solid #ccc;
      border-radius: 6px;
      font-size: 16px;
    }

    button {
      padding: 14px;
      border: none;
      border-radius: 8px;
      background: #0066cc;
      color: white;
      font-size: 16px;
      cursor: pointer;
      transition: background 0.2s ease;
    }

    button:hover {
      background: #004999;
    }
  </style>
</head>

<body>
  <h1>お問い合わせ</h1>

  <form id="contact-form">
    <div>
      <label for="name">お名前</label>
      <input type="text" id="name" name="name" required>
    </div>

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

    <div>
      <label for="message">お問い合わせ内容</label>
      <textarea id="message" name="message" rows="5" required></textarea>
    </div>

    <button type="submit">送信</button>
  </form>

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

    form.addEventListener('submit', async (e) => {
      e.preventDefault();

      const formData = new FormData(form);
      const data = Object.fromEntries(formData);

      try {
        const response = await fetch('/.netlify/functions/form-handler', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(data),
        });

        if (response.ok) {
          alert('送信が完了しました!');
          form.reset();
        } else {
          alert('送信に失敗しました。もう一度お試しください。');
        }
      } catch (error) {
        console.error('Error:', error);
        alert('エラーが発生しました。');
      }
    });
  </script>
</body>
</html>

実装結果

お問い合わせフォーム

お問い合わせ

ステップ3:Netlify Functionの作成

// netlify/functions/form-handler.js
const nodemailer = require('nodemailer');

exports.handler = async (event, context) => {
  // CORSヘッダーの設定
  const headers = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Headers': 'Content-Type',
    'Access-Control-Allow-Methods': 'POST, OPTIONS',
  };

  // プリフライトリクエストへの対応
  if (event.httpMethod === 'OPTIONS') {
    return {
      statusCode: 200,
      headers,
      body: '',
    };
  }

  // POSTメソッドのみ許可
  if (event.httpMethod !== 'POST') {
    return {
      statusCode: 405,
      headers,
      body: JSON.stringify({ error: 'Method Not Allowed' }),
    };
  }

  try {
    // リクエストボディのパース
    const { name, email, message } = JSON.parse(event.body);

    // バリデーション
    if (!name || !email || !message) {
      return {
        statusCode: 400,
        headers,
        body: JSON.stringify({ error: '必須項目が入力されていません' }),
      };
    }

    // メール送信の設定
    const transporter = nodemailer.createTransport({
      host: process.env.SMTP_HOST,
      port: process.env.SMTP_PORT,
      secure: true,
      auth: {
        user: process.env.SMTP_USER,
        pass: process.env.SMTP_PASS,
      },
    });

    // メール内容
    const mailOptions = {
      from: process.env.SMTP_USER,
      to: 'admin@example.com',
      subject: `【お問い合わせ】${name}様より`,
      text: `
お名前: ${name}
メールアドレス: ${email}
お問い合わせ内容:
${message}
      `,
      html: `
        <h2>お問い合わせがありました</h2>
        <p><strong>お名前:</strong> ${name}</p>
        <p><strong>メールアドレス:</strong> ${email}</p>
        <p><strong>お問い合わせ内容:</strong></p>
        <p>${message.replace(/\n/g, '<br>')}</p>
      `,
    };

    // メール送信
    await transporter.sendMail(mailOptions);

    return {
      statusCode: 200,
      headers,
      body: JSON.stringify({ message: '送信が完了しました' }),
    };
  } catch (error) {
    console.error('Error:', error);
    return {
      statusCode: 500,
      headers,
      body: JSON.stringify({ error: 'サーバーエラーが発生しました' }),
    };
  }
};

ステップ4:package.jsonの設定

{
  "name": "jamstack-form",
  "version": "1.0.0",
  "dependencies": {
    "nodemailer": "^6.9.0"
  }
}

ステップ5:環境変数の設定

Netlifyの管理画面で、以下の環境変数を設定します。

SMTP_HOST=smtp.gmail.com
SMTP_PORT=465
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password

ポイント: Gmailを使う場合は、アプリパスワードを生成して使用します。


実装方法2:Vercel Serverless Functionsを使ったフォーム処理

生徒

Vercelはどんな機能があるんでしょうか?

ペン博士

Vercelは、Next.jsとの統合が強力で、TypeScriptのサポートも充実しているぞい!

プロジェクト構造

my-jamstack-site/
├── public/
│   └── index.html
├── api/
│   └── form-handler.ts
└── package.json

Serverless Functionの作成(TypeScript)

// api/form-handler.ts
import type { VercelRequest, VercelResponse } from '@vercel/node';
import nodemailer from 'nodemailer';

export default async function handler(
  req: VercelRequest,
  res: VercelResponse
) {
  // CORSヘッダーの設定
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

  // プリフライトリクエスト
  if (req.method === 'OPTIONS') {
    return res.status(200).end();
  }

  // POSTメソッドのみ許可
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method Not Allowed' });
  }

  try {
    const { name, email, message } = req.body;

    // バリデーション
    if (!name || !email || !message) {
      return res.status(400).json({ error: '必須項目が入力されていません' });
    }

    // メールアドレスの形式チェック
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(email)) {
      return res.status(400).json({ error: 'メールアドレスの形式が正しくありません' });
    }

    // メール送信設定
    const transporter = nodemailer.createTransport({
      host: process.env.SMTP_HOST,
      port: Number(process.env.SMTP_PORT),
      secure: true,
      auth: {
        user: process.env.SMTP_USER,
        pass: process.env.SMTP_PASS,
      },
    });

    // メール送信
    await transporter.sendMail({
      from: process.env.SMTP_USER,
      to: 'admin@example.com',
      subject: `【お問い合わせ】${name}様より`,
      text: `
お名前: ${name}
メールアドレス: ${email}
お問い合わせ内容:
${message}
      `,
    });

    return res.status(200).json({ message: '送信が完了しました' });
  } catch (error) {
    console.error('Error:', error);
    return res.status(500).json({ error: 'サーバーエラーが発生しました' });
  }
}

フォームからの送信

// フロントエンドからの送信
const response = await fetch('/api/form-handler', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(data),
});

実装方法3:フォーム送信サービスを利用(コード不要)

生徒

コードを書かずにフォームを実装する方法はありませんか?

ペン博士

もちろんあるぞい!Netlify FormsやFormspreeなどのサービスを使えば、コード不要でフォームが実装できるんじゃ!

Netlify Formsの実装

Netlifyが提供する標準のフォーム機能です。HTMLにnetlify属性を追加するだけで使えます。

<form name="contact" method="POST" data-netlify="true">
  <input type="hidden" name="form-name" value="contact" />

  <div>
    <label for="name">お名前</label>
    <input type="text" id="name" name="name" required>
  </div>

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

  <div>
    <label for="message">お問い合わせ内容</label>
    <textarea id="message" name="message" required></textarea>
  </div>

  <button type="submit">送信</button>
</form>

メリット

  • 実装が簡単:HTMLに属性を追加するだけ
  • 管理画面で確認:送信されたデータをNetlifyの管理画面で確認できる
  • スパム対策:reCAPTCHAとの統合が可能

デメリット

  • 無料枠の制限:月100件まで(有料プランで拡張可能)
  • カスタマイズ性が低い:独自の処理を追加しにくい

Formspreeの実装

Formspreeは、外部のフォーム送信サービスです。任意のサイトで使用できます。

<form action="https://formspree.io/f/YOUR_FORM_ID" method="POST">
  <label for="name">お名前</label>
  <input type="text" name="name" required>

  <label for="email">メールアドレス</label>
  <input type="email" name="_replyto" required>

  <label for="message">お問い合わせ内容</label>
  <textarea name="message" required></textarea>

  <button type="submit">送信</button>
</form>

メリット

  • どのプラットフォームでも使える:Netlify以外でも利用可能
  • スパム対策:自動的にスパムフィルタリング
  • メール通知:フォーム送信時にメールで通知

デメリット

  • 無料枠の制限:月50件まで
  • 外部依存:Formspreeのサービスに依存する

セキュリティ対策

1. バリデーションの実装

フロントエンドとバックエンドの両方でバリデーションを実装します。

// サーバー側のバリデーション例
function validateEmail(email) {
  const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return re.test(email);
}

function validateInput(data) {
  if (!data.name || data.name.length > 100) {
    throw new Error('お名前は100文字以内で入力してください');
  }

  if (!validateEmail(data.email)) {
    throw new Error('メールアドレスの形式が正しくありません');
  }

  if (!data.message || data.message.length > 1000) {
    throw new Error('お問い合わせ内容は1000文字以内で入力してください');
  }
}

2. レート制限(Rate Limiting)

同一IPアドレスからの連続送信を制限します。

// 簡易的なレート制限の実装例
const rateLimit = new Map();
function checkRateLimit(ip) {
  const now = Date.now();
  const windowMs = 60 * 1000; // 1分
  const maxRequests = 5; // 1分間に5回まで
  if (!rateLimit.has(ip)) {
    rateLimit.set(ip, []);
  }
  const requests = rateLimit.get(ip).filter(time => now - time < windowMs);
  if (requests.length >= maxRequests) {
    throw new Error('送信回数が多すぎます。しばらくしてから再度お試しください。');
  }
  requests.push(now);
  rateLimit.set(ip, requests);
}

3. reCAPTCHAの導入

GoogleのreCAPTCHA v3導入し、ボット対策を強化します。

<!-- フロントエンド -->
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY"></script>

<script>
async function handleSubmit(e) {
  e.preventDefault();

  // reCAPTCHAトークン取得
  const token = await grecaptcha.execute('YOUR_SITE_KEY', { action: 'submit' });

  const formData = new FormData(form);
  const data = Object.fromEntries(formData);
  data.recaptchaToken = token;

  // サーバーに送信
  await fetch('/api/form-handler', {
    method: 'POST',
    body: JSON.stringify(data),
  });
}
</script>
// サーバー側でreCAPTCHAトークンを検証
async function verifyRecaptcha(token) {
  const response = await fetch('https://www.google.com/recaptcha/api/siteverify', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: `secret=${process.env.RECAPTCHA_SECRET_KEY}&response=${token}`,
  });
  const data = await response.json();
  if (!data.success || data.score < 0.5) {
    throw new Error('reCAPTCHA検証に失敗しました');
  }
}

4. 環境変数の適切な管理

APIキーやパスワードは環境変数で管理し、コードに直接書かないようにします。


トラブルシューティング

生徒

Jamstackフォーム処理を対応するにあたってトラブルシューティングのことも知りたいです!

ペン博士

うむ。ここでは、主なトラブルシューティングを3つ紹介するぞい!

問題1: メールが送信されない

原因と解決法:

  • SMTP設定が間違っている → 環境変数を確認
  • Gmailのアプリパスワードが無効 → Googleアカウントで2段階認証を有効化してアプリパスワードを生成
  • ポート番号が間違っている → 465(SSL)または587(TLS)を使用

問題2: CORSエラーが発生する

原因と解決法:

  • CORSヘッダーが設定されていない → レスポンスにAccess-Control-Allow-Originヘッダーを追加
  • OPTIONSメソッドに対応していない → プリフライトリクエストを処理するコードを追加

問題3: 関数がタイムアウトする

原因と解決法:

  • メール送信に時間がかかっている → 非同期処理を確認
  • タイムアウト設定が短い → Netlify/Vercelの設定でタイムアウト時間を延長

生徒

Jamstackサイトでも、サーバーレス関数を使えば簡単にフォームが実装できるんですね!

ペン博士

その通りじゃ!まずはNetlify FormsやFormspreeなどのサービスから始めて、カスタマイズが必要になったらサーバーレス関数を実装するのがおすすめじゃぞ!

生徒

はい!次のプロジェクトでNetlify Functionsを使ったフォーム処理に挑戦してみます!ありがとうございました!


まとめ

本記事では、Jamstackサイトのフォーム処理をサーバーレスで実装する方法について、複数のプラットフォームを使った実装例とともに詳しく解説しました。

重要なポイントは以下の通りです。

・Jamstackの課題:静的サイトではバックエンド処理ができないため、サーバーレス関数が必要
・実装方法:Netlify Functions、Vercel Serverless Functions、フォーム送信サービスの3つ
・推奨方法:初心者はNetlify FormsやFormspree、カスタマイズが必要ならサーバーレス関数
・セキュリティ対策:バリデーション、レート制限、reCAPTCHA、環境変数管理が重要
・選定基準:プロジェクトの規模、カスタマイズ性、予算に応じて最適な方法を選択

WithCodeで学んだHTML・CSS・JavaScriptの基礎知識に、サーバーレス関数の技術を組み合わせれば、どんな実案件でも対応できます。

Jamstackサイトのフォーム処理は、サーバーレス関数を使えばシンプルに実装できます。ぜひ実際のプロジェクトで活用し、高速で安全なWebサイトを構築してください。

WithCodeを体験できる初級コース公開中!

初級コース(¥49,800)が完全無料に!

  • 期間:1週間
  • 学習内容:
    ロードマップ/基礎知識/環境構築/HTML/CSS/LP・ポートフォリオ作成
    正しい学習方法で「確かな成長」を実感できるカリキュラム。

副業・フリーランスが主流になっている今こそ、自らのスキルで稼げる人材を目指してみませんか?

未経験でも心配することありません。初級コースを受講される方の大多数はプログラミング未経験です。まずは無料カウンセリングで、悩みや不安をお聞かせください!

この記事を書いた人

WithCode(ウィズコード)は「目指すなら稼げる人材」をビジョンに、累計400名以上のフリーランスを輩出してきた超実践型プログラミングスクールです。150社以上の実案件支援を特徴にWeb制作・Webデザインなどの役立つ情報を現場のノウハウに基づいて発信していきます。

– service –WithCodeの運営サービス

  • WithCode
    - ウィズコード -

    スクール

    「未経験」から
    現場で通用する
    スキルを身に付けよう!

    詳細はこちら
  • WithFree
    - ウィズフリ -

    実案件サポート

    制作会社のサポート下で
    実務経験を積んでいこう!

    詳細はこちら
  • WithCareer
    - ウィズキャリ -

    就転職サポート

    大手エージェントのサポート下で
    キャリアアップを目指そう!

    詳細はこちら

公式サイト より
今すぐ
無料カウンセリング
予約!

目次