Next.jsでreCapture V3を導入!
Next.js(App Router)で Google reCAPTCHA v3 を導入する方法
🎯 目的
フォーム送信時に、ボットによるスパムを防ぐために Google reCAPTCHA v3 を導入します。
Next.js の App Router 環境に対応した、クライアント〜サーバー検証までの構成をわかりやすくまとめます。
reCapture V3 の仕組み
- FormのSubmit時にフロントサイドのscriptでtoken(唯一の)を発行
- フォームの処理を行う時に一緒にtokenをバックエンドに送り、バックエンドで検査
- バックエンドでgoogle APIにtokenとSecret Keyを送る
- 検査した結果(0 ~ 1の値)をバックエンドに返す
- 開発者自身が定めた値以上であればバックエンドの処理(その他のフォームデータの処理)を進めるようにコーディング
✅ ステップ 1:Google reCAPTCHA v3 の設定
まずは Google の reCAPTCHA 管理ページ にアクセスし、以下を設定します:
- タイプ:「reCAPTCHA v3」
- 動作させるドメインの設定(複数設定できます!)
- 開発環境:
localhost - 本番環境:
your-domain.com
- 開発環境:
発行される以下のキーを控えます:
- サイトキー(クライアント用)
- シークレットキー(サーバー検証用)
✅ ステップ 2:環境変数の設定
.env.local に以下を追加:
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=(サイトキー)
RECAPTCHA_SECRET_KEY=(シークレットキー)JavaScript注意:NEXT_PUBLIC_ を付けることで、クライアント側でも読み込めるようになります。
サイトキーはクライアント側で使うため、必ずNEXT_PUBLICを使う
✅ ステップ 3:<Script> タグで reCAPTCHA を読み込む
app/layout.tsx 内で reCAPTCHA のスクリプトを読み込む必要があります。
このステップを忘れると 、ステップ4で使用するgrecaptcha が未定義になります。
なお、strategy=”beforeInteractive” は、「表示するpage.tsxのJSを実行する前に実行させる」という意味。(ただし、page.tsxのJS実行時に、Scriptタグの実行完了は保証しない)
import Script from "next/script";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="ja">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<Script
strategy="beforeInteractive"
src={`https://www.google.com/recaptcha/api.js?
render=${process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}`}
/>
<Header />
<main className="min-h-screen --foreground p-6">{children}</main>
<Analytics />
<Footer />
</body>
</html>
);
}Layout.tsx✅ ステップ 4:クライアントでトークンを取得する
grecaptcha.execute() を使って、トークンを取得します。
取得したトークンを FormData に追加して、サーバーへ送信します。
- excecuteの引数
- 第一引数はsite key
- 第二引数はオブジェクト
{action: “ユーザーのアクション(submit, checkout, loginなど任意の文字列)”}
import { zodResolver } from "@hookform/resolvers/zod";
import React from "react";
import { useForm } from "react-hook-form";
import { validationSchema } from "../utils/validationSchema";
import { sendEmail } from "../contact/actions";
import { captureRecaptchaToken } from "../utils/capture";
interface FormData {
name: string;
email: string;
contents: string;
}
const ContactForm = () => {
...
const onSubmit = async (data: FormData) => {
setIsSending(true);
setIsSuccess(false);
setIsError(false);
const token = await captureRecaptchaToken(); //token出力の関数 *別ファイルに記載
const formData = new FormData();
formData.append("name", data.name);
formData.append("email", data.email);
formData.append("contents", data.contents);
formData.append("g-recaptcha-response", token as string); //append the token to formData
const result = await sendEmail(formData); //send formData with token to the backend.
setIsSending(false);
if (result.success) {
setIsSuccess(true);
setTimeout(() => {
setIsSuccess(false);
}, 3000);
} else {
setIsError(true);
console.error("送信エラー:", JSON.stringify(result.errors, null, 2));
setTimeout(() => {
setIsError(false);
}, 3000);
}
};
return (
<form
onSubmit={handleSubmit(onSubmit)}
...JavaScript//戻り値はPromise型 → 呼び出し時はawaitかthenが必要
export async function captureRecaptchaToken(): Promise<string | null> {
return new Promise((resolve) => {
if (typeof window.grecaptcha ==="undefined") {
console.error("reCAPTCHA がロードされていません");
resolve(null);
return;
}
window.grecaptcha.ready(async () => {
const siteKey = process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY;
if (!siteKey) {
console.error("reCAPTCHA site key が設定されていません");
resolve(null);
return;
}
try {
const token = await window.grecaptcha.execute(siteKey, {
action: "submit",
}); //ここでtoken発行
resolve(token);
} catch (error) {
console.error("reCAPTCHA 実行エラー:", error);
resolve(null);
}
});
});
}JavaScriptreCAPTCHA の execute() を使うには、まず grecaptcha.ready() で準備ができるのを待つ必要がある。
ただし、ready() は非同期関数ではなく Promise を返さないため、await で待つことはできない。
→ そのため、new Promise を使って「待てる形」にラップしてあげる必要がある。
✅ ステップ 5:サーバーでトークンを検証する
取得したトークンをサーバー側で Google に送信し、以下を確認します:
//バックエンドに(フォームデータと一緒に)トークンを送信
const result = await sendEmail(formData); JavaScript//バックエンドでの受け取りと処理
export async function sendEmail(formData: FormData) {
const token = formData.get("g-recaptcha-response")?.toString();
const recaptchaSecretKey = process.env.RECAPTURE_SECRET_KEY;
if (!token || !recaptchaSecretKey) {
return {
success: false,
errors: {
message: "reCAPTCHAのトークンが取得できませんでした。",
},
};
}
const url = new URL("https://www.google.com/recaptcha/api/siteverify");
//apiにシークレットキーとtokenを送信
const recaptchaRes = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
secret: recaptchaSecretKey,
response: token,
}),
});
const recaptchaData = await recaptchaRes.json(); //検証結果をjson文字列で受け取りJavaScriptバックエンドではtokenとシークレットキーをAPIに送付して、検証させ結果を受け取る
結果のjson
{
success: true | false,
score: 0.0 ~ 1.0,
action: submit | login | checkout | ...
}JavaScriptsuccess: trueかscore(0.0〜1.0)がスパム判定しきい値以上かaction(オプション)
スコアのしきい値は 0.5〜0.7 くらいがおすすめです。
✅ ステップ 6:スコアが低い場合は処理を中断
スコアが低い、success: false、browser-error などが返ってきた場合、
「スパム判定」としてメール送信などの処理をブロックします。
//successプロパティがfalse, もしくはscoreが0.4以下はスパムと判定し、フロントエンドにエラーメッセージなどを返す
if (!recaptchaData.success || recaptchaData.score < 0.4) {
return { success: false, errors: ["スパム検出されました"] };
}
... 問題がない場合の処理 ...JavaScript✅ ステップ 7:フォーム送信処理(メール送信やDB保存など)
フォーム内容が妥当か(Zodなどでバリデーション)チェックした上で、メール送信や保存などを行います。
✅ よくあるエラーと対処
| エラーコード | 原因 | 対処 |
|---|---|---|
browser-error | トークンの送信形式ミス、スクリプト未ロード、ドメイン未登録など | POST形式の見直し・ドメイン確認・grecaptchaロード確認 |
score < 0.5 | Googleが「怪しい」と判断した | スパム扱いで弾くか、しきい値を調整 |
success: false | トークン無効・期限切れなど | トークン再取得・短時間で送信する |
✅ まとめ
Next.js App Router での reCAPTCHA v3 導入は以下が重要です:
- Googleでの登録とキーの取得
<Script>タグによるスクリプト読み込み- クライアントでのトークン取得と送信
- サーバーでの正しい POST 検証(Content-Type + URLSearchParams)
- スコアに基づいた判定処理
セキュリティを高めつつ、ユーザー体験を損なわないように活用しましょう。