0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「Next.js × Vercel でお問い合わせフォームを実装💌✨ スパム対策もバッチリ!」

Posted at

1. Next.jsプロジェクトの作成

まずは、新規Next.jsプロジェクトを作成します。以下のコマンドを実行して、プロジェクトディレクトリを用意してください。

npx create-next-app contact-form-with-vercel
cd contact-form-with-vercel

2. フロントエンドフォームの作成

次に、ユーザーから名前、メールアドレス、メッセージを入力してもらうフォームを実装します。pages/contact.js(またはTypeScriptの場合は.tsx)に以下のコードを追加してください。フォームは送信時にPOSTリクエストをAPIルートへ送るようにしています。

import { useState } from "react";

export default function Contact() {
  const [form, setForm] = useState({ name: "", email: "", message: "" });
  const [status, setStatus] = useState("");

  const handleChange = (e) => {
    setForm({ ...form, [e.target.name]: e.target.value });
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setStatus("送信中...");

    // ★★ ここでreCAPTCHAトークンを取得する処理を後ほど追加可能 ★★

    try {
      const response = await fetch("/api/contact", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(form),
      });

      if (response.ok) {
        setStatus("送信成功!");
        setForm({ name: "", email: "", message: "" });
      } else {
        setStatus("送信失敗。もう一度お試しください。");
      }
    } catch (error) {
      setStatus("エラーが発生しました。");
    }
  };

  return (
    <div>
      <h1>お問い合わせフォーム</h1>
      <form onSubmit={handleSubmit}>
        <label>
          お名前:
          <input
            type="text"
            name="name"
            value={form.name}
            onChange={handleChange}
            required
          />
        </label>
        <br />
        <label>
          メールアドレス:
          <input
            type="email"
            name="email"
            value={form.email}
            onChange={handleChange}
            required
          />
        </label>
        <br />
        <label>
          メッセージ:
          <textarea
            name="message"
            value={form.message}
            onChange={handleChange}
            required
          ></textarea>
        </label>
        <br />
        {/* ★★ Honeypotフィールド ★★ */}
        <input type="text" name="honeypot" style={{ display: "none" }} />
        <button type="submit">送信</button>
      </form>
      <p>{status}</p>
    </div>
  );
}

3. バックエンドAPIルートの作成

pages/api/contact.js にAPIルートを用意し、フォームから送信されたデータを処理します。ここでは、Nodemailer を使用してメール送信を実装します。また、スパム対策として後述するreCAPTCHAやHoneypotの検証処理も組み込みます。

APIルートのコード例

import nodemailer from "nodemailer";
import fetch from "node-fetch";

export default async function handler(req, res) {
  if (req.method === "POST") {
    const { name, email, message, recaptchaToken, honeypot } = req.body;

    // Honeypotフィールドが埋まっていたらスパムとしてリジェクト
    if (honeypot) {
      return res.status(400).json({ error: "スパムが検出されました。" });
    }

    // reCAPTCHAが導入されている場合、トークンの検証を実行
    if (recaptchaToken) {
      const verificationResponse = 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=${recaptchaToken}`,
        }
      );
      const verificationResult = await verificationResponse.json();

      if (!verificationResult.success) {
        return res.status(400).json({ error: "reCAPTCHA検証に失敗しました。" });
      }
    }

    // Nodemailerの設定
    const transporter = nodemailer.createTransport({
      host: process.env.SMTP_HOST,
      port: Number(process.env.SMTP_PORT),
      secure: process.env.SMTP_SECURE === "true",
      auth: {
        user: process.env.SMTP_USER,
        pass: process.env.SMTP_PASS,
      },
    });

    try {
      await transporter.sendMail({
        from: process.env.SMTP_USER,
        to: process.env.CONTACT_RECEIVER,
        subject: `お問い合わせ:${name}`,
        text: `名前: ${name}\nメールアドレス: ${email}\nメッセージ:\n${message}`,
      });

      res.status(200).json({ success: true });
    } catch (error) {
      console.error("メール送信エラー:", error);
      res.status(500).json({ error: "メール送信に失敗しました。" });
    }
  } else {
    res.status(405).json({ error: "メソッドが許可されていません。" });
  }
}

環境変数の設定

ルートディレクトリに .env.local ファイルを作成し、以下のようにSMTPサーバー情報やreCAPTCHAのシークレットキーを設定してください。

SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=your-smtp-user@example.com
SMTP_PASS=your-smtp-password
CONTACT_RECEIVER=receiver@example.com
RECAPTCHA_SECRET_KEY=your-recaptcha-secret-key

4. スパム対策の追加

ここでは、無料で利用できる2種類のスパム防止策を紹介します。

4.1 Google reCAPTCHA v3 の導入

Google reCAPTCHAは、スパム対策として非常に有効です。以下の手順で実装します。

  1. Google reCAPTCHAに登録
    Google reCAPTCHAのサイトで、サイトキーとシークレットキーを取得してください。

  2. フロントエンドでのトークン取得
    フォーム送信時に grecaptcha.execute を呼び出し、トークンを取得してAPIルートへ送信します。例えば、handleSubmit 内に以下の処理を追加します。

    const handleSubmit = async (e) => {
      e.preventDefault();
      setStatus("送信中...");
    
      // reCAPTCHAトークンの取得(事前にGoogleのスクリプトを読み込む必要あり)
      const token = await grecaptcha.execute("YOUR_SITE_KEY", { action: "submit" });
      
      try {
        const response = await fetch("/api/contact", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ ...form, recaptchaToken: token, honeypot: "" }),
        });
    
        if (response.ok) {
          setStatus("送信成功!");
          setForm({ name: "", email: "", message: "" });
        } else {
          setStatus("送信失敗。もう一度お試しください。");
        }
      } catch (error) {
        setStatus("エラーが発生しました。");
      }
    };
    
  3. API側でのトークン検証
    前述のAPIルートコード内で、recaptchaTokenの検証を実施しています。検証に成功すれば、メール送信処理へ進む仕組みです。

4.2 Honeypotフィールドの導入

Honeypotは、通常ユーザーには見えない隠しフィールドを利用してボット対策を行うシンプルな方法です。

  • フロントエンドには隠しフィールドを追加(上記フォーム内に実装済み)
  • APIルートでフィールドの値が空でない場合は、リクエストを却下
if (honeypot) {
  return res.status(400).json({ error: "スパムが検出されました。" });
}

5. Vercelへのデプロイ

最後に、完成したプロジェクトをVercelにデプロイします。Vercel CLIやGitHub連携を利用してデプロイ可能です。CLIを用いる場合は、以下のコマンドを実行してください。

vercel deploy

フォーム送信フローの図解

以下のシンプルなフローチャートは、フォーム送信からメール送信までの流れを示しています。


結論

この記事では、Next.jsとVercelを利用して基本的なお問い合わせフォームを構築し、ノード環境でのメール送信(Nodemailer)とともに、スパム対策としてGoogle reCAPTCHA v3およびHoneypotを組み込む方法を解説しました。

  • Next.jsのページとAPIルートを組み合わせることで、シンプルながら柔軟性の高いフォーム実装が可能です。
  • スパム対策は、ユーザー体験を損なうことなく、確実にボットからの不正送信を防止します。

この手順を参考に、実際のプロジェクトでお問い合わせフォームを実装してみてください。エンジニア初心者でも取り組みやすい内容になっていますので、ぜひ挑戦してみましょう!


💖 ご支援いただけませんか?

スクリーンショット 2025-01-31 7.51.39.png

このブログでは、高品質な情報提供と学習活動を通じて、読者の皆さまのお役に立つことを目指しています。もしこの記事が役立ったと感じていただけましたら、ご支援いただけると幸いです!


暗号資産による寄付

以下のウォレットアドレスをご利用ください。重要:Ethereum (ETH)、BNB Chain (BNB)、Polygon (MATIC)、Avalanche (AVAX) は、全て以下の同一アドレスを使用しますが、送金ネットワークの選択を間違えると資金が失われます! 送金時には、絶対に使用するネットワーク(例: ERC-20、BEP-20、Polygon、Avalanche C-Chain)を必ず正しく選択してください。


Ethereum Logo

Ethereum (ETH) (ネットワーク: ERC-20)

0x5CDA2F68f59F641B00aD172475c3d5fC10321174
BNB Logo

BNB Chain (BNB) (ネットワーク: BEP-20)

0x5CDA2F68f59F641B00aD172475c3d5fC10321174
Polygon Logo

Polygon (MATIC) (ネットワーク: Polygon)

0x5CDA2F68f59F641B00aD172475c3d5fC10321174  
Avalanche Logo

Avalanche (AVAX) (ネットワーク: Avalanche C-Chain)

0x5CDA2F68f59F641B00aD172475c3d5fC10321174
Solana Logo

Solana (SOL)

EnPFbqDbF67rU9mAPvfgh4YYtncJNbFQ9NLQ5R6z5S2f
Stellar Logo

Stellar (XLM) メモ: 必要に応じて入力してください。

GCSMWCACKVEZ737GZAV4AJRFL52ZZKVQ7M3B3KYY64JJGOAO2GDYKABO 
Ripple Logo

Ripple (XRP) タグ: 必要に応じて入力してください。

r1s4EASr3zQRrfpDA3ptTahezBhGo2hhN
Cardano Logo

Cardano (ADA)

addr1q8heq6ddw8rwlqa5hqlucnfk36arah9tzc8ajxvu83870h7lrre25wzq9yemex857we56cm0xu8tmxqvm8nykmtgsjdqavdpv7
Dogecoin Logo

Dogecoin (DOGE)

DRFZ9JhAk3DTtu1tV85cawekWNrm1vKm3H

資金用途

寄付金は以下の目的で活用させていただきます:

  1. サーバー維持費やデザインツール購入
  2. 学習活動(オンラインコース受講・書籍購入)
  3. 読者向け無料コンテンツ制作

ご協力いただいた皆さまには心より感謝申し上げます! 🙏


補足情報

  • Ethereum (ETH)、BNB Chain (BNB)、Polygon (MATIC)、Avalanche (AVAX)について
    上記4つのネットワークは同じウォレットアドレス0x5CDA2F68f59F641B00aD172475c3d5fC10321174)を使用します。ただし、送金時には、絶対に使用するネットワーク(例: ERC-20、BEP-20、Polygon、Avalanche C-Chain)を必ず正しく選択してください。

  • USDCやUSDTなどのステーブルコインも、対応するネットワーク経由であれば送金可能です。ただし、送金先のネットワークと選択するネットワークが一致していることを必ず確認してください。

  • 初回送金時には少額でテスト送金することをおすすめします。


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?