お問い合わせフォームの「スパム問題」、まだ放置していませんか?
Next.jsによる高速なWebサイト構築が主流となる中、ビジネスの窓口である「お問い合わせフォーム」の重要性は変わりません。しかし、このフォームは常にボットによるスパム(迷惑行為)の標的となっています。
フォームを設置したものの、毎日大量のスパムメールが届き、本当に重要なリード(見込み客)からのお問い合わせが埋もれてしまう…。そんな経験をお持ちの開発者やWeb担当者も多いのではないでしょうか。
従来のスパム対策(v2チェックボックス)の限界
「私はロボットではありません」でお馴染みのGoogle reCAPTCHA v2(チェックボックス式)は、長らくスパム対策のデファクトスタンダードでした。しかし、これには大きな欠点があります。
- UXの悪化: ユーザーに「クリック」や「画像選択」という余計な操作を強いるため、フォーム送信の離脱率(CVRの低下)に繋がります。
- 対策の陳腐化: 近年では、AIの進化によりv2チェックボックスを突破するボットも増えています。
Next.js時代の「スマートな」スパム対策とは?
現代のWeb開発、特にNext.jsのようなフレームワークを使う場合、UXを最優先に考えるべきです。ユーザーにストレスを感じさせず、水面下でボットを判定する「スマートな」対策が求められます。
本記事で提案するのは、まさにそのアプローチです。UXを一切損なわずに、複数の防衛線を張ることで、フォームのセキュリティを格段に高めます。
スパム対策の甘さが招く、深刻なビジネスリスク
「少しぐらいスパムが来ても、手動で削除すればいい」と考えるかもしれません。しかし、対策の甘さは、目に見えないところで深刻なビジネスリスクを引き起こします。
機会損失:本物の問い合わせがスパムに埋もれる
最大の損失はこれです。1日に100件のスパムが届けば、その中に紛れ込んだ「1件の貴重な商談」を見逃すリスクは飛躍的に高まります。営業担当がスパムの仕分けに疲弊し、結果として迅速な顧客対応(リードへのレスポンス)ができなくなるのです。
サーバー負荷とセキュリティ懸念
大量のボットアクセスは、無駄なサーバーリソースを消費します。また、悪意のあるスクリプトがフォームを通じて送信される(メールヘッダーインジェクションなど)可能性もゼロではなく、セキュリティ上の懸念も残ります。
「ちゃんとした」フォームとは、単にメールが送れるだけでなく、こうしたビジネスリスクを未然に防ぐ仕組みが組み込まれたフォームのことを指します。
解決策:Next.js + 3つの防衛線で「ちゃんとした」フォームを作る
そこで本記事では、Next.js (App Router) 環境を前提に、以下の3つの技術を組み合わせて「UXを損なわない」強力なスパム対策を実現します。
なぜこの3つ? 各技術の役割
1. Honeypot (ハニーポット)
役割:最も単純なボットの排除
人間には見えない(CSSで隠す)入力フィールドをフォームに設置します。人間は入力しないため、このフィールドに値が入っていたら、それはボットだと判断できます。古典的ですが、単純なボット対策に非常に有効です。
2. Google reCAPTCHA v3
役割:高度なボットの判定
v2とは異なり、ユーザーに操作を強いることなく、サイト上での行動(マウスの動き、クリックパターンなど)を分析し、「人間らしさ」をスコア(0.0〜1.0)で判定します。このスコアが低い(ボットの可能性が高い)リクエストを弾くことができます。
3. Resend
役割:確実なメール送信と開発体験の向上
モダンなメール送信APIサービスです。従来のSMTP設定の煩雑さから開発者を解放し、シンプルなAPIコールでメール送信を実現します。Next.jsとの親和性が非常に高いのも特徴です。
これらを組み合わせることで、「単純なボットはHoneypotで弾き、高度なボットはreCAPTCHA v3で弾き、人間からの(と判断された)リクエストのみをResendで処理する」という多層防御が完成します。
実装ガイド:Next.js (App Router) での構築ステップ
ここからは、Next.jsのApp RouterとServer Actionsを使い、具体的な実装手順を解説します。コードは簡潔化していますが、実際のプロジェクトに応用できるはずです。
ステップ1:環境構築とライブラリの準備
まず、必要なライブラリをインストールします。reCAPTCHA v3の実行には google-recaptcha-v3、Resendの利用には resend を使います。フォームのバリデーションには zod が便利です。
npm install resend zod google-recaptcha-v3
また、環境変数 (`.env.local`) に各種シークレットキーを登録しておきます。
# .env.local
RESEND_API_KEY=re_xxxxxxxxxxxx
RECAPTCHA_V3_SECRET_KEY=xxxxxxxxxxxx
NEXT_PUBLIC_RECAPTCHA_V3_SITE_KEY=xxxxxxxxxxxx
ステップ2:Honeypot(ハニーポット)の実装
フォームコンポーネントに、ボットを罠にかけるためのHoneypotフィールドを追加します。`position: absolute` と opacity: 0 などで、人間のユーザーには絶対に見えない・操作できないようにCSSで隠すのが一般的です。
// components/ContactForm.jsx
// ...(フォームの他の部分)
{/* Honeypot Field: 見えないようにCSSで隠す */}
<div style={{ position: 'absolute', left: '-5000px', opacity: '0' }} aria-hidden="true">
<label htmlFor="honeypot">Bot detected</label>
<input type="text" id="honeypot" name="honeypot" tabIndex={-1} autoComplete="off" />
</div>
// ...
サーバーサイド(Server Actions)では、この honeypot フィールドに値が入っていたら、即座に処理を中断します。
ステップ3:Google reCAPTCHA v3 の設定と導入
reCAPTCHA v3は、フロントエンドでトークンを取得し、バックエンド(Server Actions)でそのトークンを検証する流れになります。
まず、reCAPTCHA v3のプロバイダーでアプリケーションをラップします。(`layout.tsx` など)
// app/layout.tsx
import { GoogleReCaptchaProvider } from 'google-recaptcha-v3';
export default function RootLayout({ children }) {
const siteKey = process.env.NEXT_PUBLIC_RECAPTCHA_V3_SITE_KEY || '';
return (
<html>
<body>
<GoogleReCaptchaProvider reCaptchaKey={siteKey}>
{children}
</GoogleReCaptchaProvider>
</body>
</html>
);
}
次に、フォーム送信時にトークンを取得し、フォームデータと一緒に送信します。`useGoogleReCaptcha` フックを使います。
// components/ContactForm.jsx ('use client')
import { useGoogleReCaptcha } from 'google-recaptcha-v3';
export default function ContactForm() {
const { executeRecaptcha } = useGoogleReCaptcha();
const handleSubmit = async (formData) => {
if (!executeRecaptcha) {
console.error("reCAPTCHA not initialized");
return;
}
try {
// reCAPTCHAトークンを取得
const token = await executeRecaptcha('contactForm');
// Server Actionsにフォームデータとトークンを渡す
const result = await sendEmailAction(formData, token);
// ...(結果に応じた処理)
} catch (error) {
// ...(エラー処理)
}
};
return (
<form action={handleSubmit}>
{/* ... フォーム項目 ... */}
{/* ... Honeypotフィールド ... */}
<button type="submit">送信</button>
</form>
);
}
ステップ4:Resend によるメール送信 API の設定
Resendを使ったメール送信ロジックは非常にシンプルです。Server Actions内で実行します。
// app/actions.ts ('use server')
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendEmail(payload) {
try {
const { data, error } = await resend.emails.send({
from: '自社サイト通知 <noreply@example.com>',
to: ['your-support-email@example.com'], // お問い合わせの受信先
subject: `【お問い合わせ】${payload.name}様より`,
react: <EmailTemplate name={payload.name} email={payload.email} message={payload.message} />, // ResendはReactコンポーネントでメールテンプレートを書けます
});
if (error) {
throw new Error("Email sending failed");
}
return { success: true, data };
} catch (error) {
return { success: false, error: error.message };
}
}
ステップ5:Server Actions で全てを連携させる(バリデーションと実行)
最後に、Server Actions(`app/actions.ts`)で、Honeypotチェック、reCAPTCHA v3検証、Zodバリデーション、メール送信をすべて繋ぎ込みます。
// app/actions.ts ('use server')
import { z } from 'zod';
import { Resend } from 'resend';
// (sendEmail関数は上記ステップ4のものを想定)
// Zodスキーマ定義
const FormSchema = z.object({
name: z.string().min(1, 'お名前は必須です'),
email: z.string().email('有効なメールアドレスを入力してください'),
message: z.string().min(10, '内容は10文字以上で入力してください'),
});
// reCAPTCHA v3 検証関数
async function verifyRecaptcha(token) {
const secretKey = process.env.RECAPTCHA_V3_SECRET_KEY;
const response = await fetch(`https://www.google.com/recaptcha/api/siteverify`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `secret=${secretKey}&response=${token}`,
});
const data = await response.json();
// スコアが低い(例: 0.5未満)または失敗した場合はボットと判断
return data.success && data.score >= 0.5;
}
// メインのServer Action
export async function sendEmailAction(formData, recaptchaToken) {
// 1. Honeypot チェック
if (formData.get('honeypot')) {
return { success: false, error: 'Bot detected' }; // ボットには失敗を悟られないよう、一般的なエラーを返しても良い
}
// 2. reCAPTCHA v3 検証
const isHuman = await verifyRecaptcha(recaptchaToken);
if (!isHuman) {
return { success: false, error: 'reCAPTCHA verification failed' };
}
// 3. Zod バリデーション
const parsed = FormSchema.safeParse({
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
});
if (!parsed.success) {
return { success: false, error: 'Validation failed', issues: parsed.error.issues };
}
// 4. メールの送信(Resend実行)
try {
await sendEmail(parsed.data); // ステップ4で定義した関数
return { success: true, message: 'お問い合わせありがとうございます。' };
} catch (error) {
return { success: false, error: 'Failed to send email' };
}
}
まとめ:UXとセキュリティを両立したフォームで、信頼を勝ち取る
今回は、Next.js (App Router) 環境において、UXを損なわずに強力なスパム対策を施す方法として、Honeypot、reCAPTCHA v3、そしてResendを組み合わせる手法を解説しました。
お問い合わせフォームは、お客様との最初の接点となる重要な場所です。そこにストレス(v2チェックボックス)やリスク(スパムの氾濫)があってはなりません。
本記事で紹介した実装を行うことで、ユーザーは快適に、そして運営者は安心してフォームを運用することが可能になります。ぜひ、あなたのNext.jsプロジェクトにも「ちゃんとした」フォームを実装してみてください。
Next.js開発のパートナーをお探しですか?
当社(株式会社ABC)は、Next.jsをはじめとするモダンなWeb技術を用いた高パフォーマンスなサイト構築を得意としています。
「自社サイトをNext.jsでリニューアルしたい」「セキュアなWebアプリケーション開発を依頼したい」など、Webに関するお困りごとがございましたら、お気軽にご相談ください。
専門のエンジニアが、お客様のビジネス課題に最適なソリューションをご提案します。

