Implementing Google reCAPTCHA v3 with Next.js Server Actions

Implementing Google reCAPTCHA v3 with Next.js Server Actions

Posted by

kamlesh paul

on

Feb 21, 2025

5 min read

Last updated on : Feb 21, 2025

163 views

Table of contents

Prerequisites

To follow along, ensure you have:

  1. A Next.js project configured with the App Router.
  2. Google reCAPTCHA v3 API keys, obtainable from the reCAPTCHA admin console:
  • Site Key: For client-side integration.
  • Secret Key: For server-side verification.

Project Setup

First, create a new environment file .env in your project root:

NEXT_PUBLIC_RECAPTCHA_SITE_KEY=your_site_key_here
RECAPTCHA_SECRET_KEY=your_secret_key_here

The NEXT_PUBLIC_ prefix makes the site key available to the client, as intended, while the secret key remains server-only. Always exclude this file from version control to prevent accidental exposure.

Implementation Steps

1. Loading the reCAPTCHA Script

reCAPTCHA v3 operates silently in the background, requiring its script to be loaded dynamically on the client. Create a file at components/ReCaptchaScript.tsx:

components/ReCaptchaScript.tsx
'use client';
 
import { useEffect } from 'react';
 
export default function ReCaptchaScript() {
  useEffect(() => {
    const loadReCaptcha = () => {
      const script = document.createElement('script');
      script.src = `https://www.google.com/recaptcha/api.js?render=${process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}`;
      script.async = true;
      script.onerror = () => console.error('reCAPTCHA script failed to load. Verify your network and site key.');
      document.body.appendChild(script);
    };
 
    loadReCaptcha();
 
    return () => {
      const script = document.querySelector(`script[src*="recaptcha"]`);
      if (script) script.remove();
    };
  }, []);
 
  return null;
}

This component loads the reCAPTCHA script on mount and includes an error handler for debugging issues like network failures or incorrect keys. The cleanup function prevents duplicate script injections during re-renders.

2. Configuring the Server Action

Next, define a Server Action to verify the reCAPTCHA token. Create :

app/actions/verifyCaptcha.action.ts
'use server';
 
interface ReCaptchaResponse {
  success: boolean;
  score: number;
  action: string;
  challenge_ts: string;
  hostname: string;
  'error-codes'?: string[];
}
 
export async function verifyCaptcha(token: string): Promise<ReCaptchaResponse> {
  try {
    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}`,
    });
 
    if (!response.ok) {
      throw new Error(`Verification failed with status: ${response.status}`);
    }
 
    const data: ReCaptchaResponse = await response.json();
 
    if (!data.success) {
      console.warn('reCAPTCHA verification unsuccessful:', data['error-codes']);
    }
 
    return data;
  } catch (error) {
    console.error('Error during reCAPTCHA verification:', error);
    throw new Error('Unable to verify reCAPTCHA at this time.');
  }
}

This function sends the token to Google’s verification endpoint, returning a response with a success flag and a score (0.0 to 1.0). Detailed logging helps diagnose issues, such as invalid keys or network errors, which Google’s documentation highlights as common pitfalls.

3. Creating the Form Component

Now, build a form that leverages this verification. Create:

components/ProtectedForm.tsx
'use client';
 
import { useState } from 'react';
import { verifyCaptcha } from '@/app/actions/verifyCaptcha.action';
 
export default function ProtectedForm() {
  const [message, setMessage] = useState('');
  const [isLoading, setIsLoading] = useState(false);
 
  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setIsLoading(true);
    setMessage('');
 
    try {
      const token = await (window as any).grecaptcha.execute(
        process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY!,
        { action: 'submit_form' }
      );
 
      const result = await verifyCaptcha(token);
 
      if (result.success && result.score >= 0.5) {
        setMessage('Form submitted successfully.');
        // Implement additional server-side logic here (e.g., database operations).
      } else {
        setMessage(`Verification failed (score: ${result.score}). Please try again.`);
      }
    } catch (error) {
      console.error('Submission error:', error);
      setMessage('An error occurred during submission. Please retry.');
    } finally {
      setIsLoading(false);
    }
  };
 
  return (
    <form onSubmit={handleSubmit} className="space-y-4 max-w-md">
      <input
        type="text"
        placeholder="Enter your message"
        className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
        disabled={isLoading}
      />
      <button
        type="submit"
        className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:bg-gray-400"
        disabled={isLoading}
      >
        {isLoading ? 'Processing...' : 'Submit'}
      </button>
      {message && <p className="text-sm text-gray-700">{message}</p>}
    </form>
  );
}

This component includes a loading state for better user feedback and a score threshold of 0.5, which can be adjusted based on your security requirements. The error handling ensures users aren’t left guessing if something goes wrong.

4. Integrating into Your Page

Update app/page.tsx to incorporate these components

app/page.tsx
import ReCaptchaScript from '@/components/ReCaptchaScript';
import ProtectedForm from '@/components/ProtectedForm';
 
export default function Home() {
  return (
    <main className="container mx-auto p-4">
      <ReCaptchaScript />
      <h1 className="text-2xl font-bold mb-4">Secure Form Submission</h1>
      <ProtectedForm />
    </main>
  );
}

This completes the setup, rendering a fully functional, bot-protected form. Below is a screenshot of the final result, showing the form in action: implementing-google-recaptcha-v3-with-nextjs-server-actions-screenshot.png

How It Works

The process is straightforward:

  1. Script Initialization: The ReCaptchaScript component loads Google’s script on page load.
  2. Token Creation: On form submission, reCAPTCHA generates a token based on user behavior.
  3. Server Verification: The Server Action validates the token with Google, receiving a score and success status.
  4. Response Handling: The application evaluates the score to determine whether to proceed with the submission.

Unlike older reCAPTCHA versions, v3 operates without user interaction, enhancing both security and usability.

Best Practices

  1. Error Handling: Always implement proper error handling for both client and server-side operations.

  2. Environment Variables: Keep your reCAPTCHA keys secure in environment variables.

  3. Score Threshold: Adjust the score threshold based on your security needs:

    if (result.success && result.score > 0.7) { // More strict

Addressing Common Challenges

  1. Script Loading Failures: Implement a retry mechanism if the script fails to load:
const loadScript = async (retries = 3) => {
if (retries === 0) throw new Error('Script load failed.');
try {
  // Script loading logic
} catch {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return loadScript(retries - 1);
}
};
  1. Token Expiration: Tokens expire after two minutes. Refresh them if needed

    if (tokenError?.includes('timeout')) {
      // Generate new token
      const newToken = await window.grecaptcha.execute(siteKey, { action: 'submit' });
    }
  2. Unexpected Scores: If legitimate users are flagged, review your reCAPTCHA configuration in the admin console and adjust the threshold as necessary.

Conclusion

Integrating Google reCAPTCHA v3 with Next.js Server Actions provides an effective, user-friendly approach to safeguarding your forms from automated abuse. This setup leverages Next.js’s server-side capabilities and reCAPTCHA’s risk-based scoring to deliver security without compromising experience.

For optimal results, secure your keys, refine your score thresholds, and consult the reCAPTCHA v3 documentation and Next.js Server Actions reference as needed.

Get updates directly to your inbox.

Join 500+ developers getting updates on Laravel & Next.js tips. No spam,
unsubscribe anytime.


Share this article:

163 views