Entelligo Logo

Guide to Implementing Google reCAPTCHA

Published: 14 Sep, 2025

Overview

Spam bots are constantly trying to fill your website's forms with junk. This messes up your data. The best way to stop them is with Google reCAPTCHA, a tool that checks if a user is a real person or a bot.

Why do we need Google Captcha?

  1. Spam Prevention is the primary reason. It blocks bots from submitting malicious or junk content through your forms.

  2. It helps prevent credential stuffing and brute-force attacks on login pages.

  3. By ensuring submissions are from real users, you maintain a clean and reliable database.

Prerequisites

  1. Next.js Project

  2. Google Account

Step 1: Get Your Google reCAPTCHA Keys

1. Go to the Google reCAPTCHA Admin Console.

Image

2. Set a name, version and add the domains where you'll use it. For local development, add localhost.

Image

3. Accept the terms of service and click Submit.

You will be provided with a Site Key and a Secret Key.

Image

Site Key: This is public and used on the client-side (your Next.js frontend).

Secret Key: This is private and must only be used on the server-side (your Next.js API routes). Never expose your Secret Key in your frontend code.

Step 2: Store the keys Securely

# For reCAPTCHA v2 or v3
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=
RECAPTCHA_SECRET_KEY=

Step 3: Implementation

1. Install the Package

pnpm add react-google-recaptcha

2. Client-Side Implementation (The Form)

Important for App Router: Remember to add the "use client"; directive at the top of your file, as this component uses state and hooks.

'use client';

import React, { useState, useRef } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import z from 'zod';
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from './ui/form';
import { Input } from './ui/input';
import links from '@/data/links';
import ReCaptcha from 'react-google-recaptcha';

const formSchema = z.object({});

export default function ContactForm() {
  const [captchaToken, setCaptchaToken] = useState<string>();
  const captchaRef = useRef<ReCaptcha>(null);

  const onSubmit = async (data: z.infer<typeof formSchema>) => {
    try {
      if (!captchaToken) {
        throw new Error('Please check the captcha');
      }

      await sendContactForm({
        ...data,
        captchaToken,
      });
      captchaRef.current?.reset();
      toast.success('Form submitted successfully!');
      form.reset();
      setCaptchaToken(undefined);
    } catch (error: Error | unknown) {
      if (error instanceof Error) {
        toast.error(error.message);
      } else {
        toast.error('Failed to submit the form. Please try again later.');
      }
    }
  };

  return (
    <div className="flex lg:flex-row flex-col space-y-4 lg:space-y-0 lg:space-x-4">
      <div className="flex-grow border-r px-6">
        <Form {...form}>
          <form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
            <div>
              <ReCaptcha
                sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY as string}
                ref={captchaRef}
                onChange={(token) => {
                  setCaptchaToken(token as string);
                }}
              />
            </div>

            <Button
              type="submit"
              disabled={isSubmitting}
              size="lg"
              className="cursor-pointer"
            >
              {isSubmitting
                ? 'Submitting...'
                : isSubmitSuccessful
                  ? 'Submitted'
                  : 'Submit'}
            </Button>
          </form>
        </Form>
      </div>
    </div>
  );
}

3. Server-Side Validation (Server Actions)

'use server';

async function isValidGoogleCaptcha(token: string) {
  const secret = process.env.RECAPTCHA_SECRET_KEY;
  const response = await fetch(
    `https://www.google.com/recaptcha/api/siteverify?secret=${secret}&response=${token}`,
    {
      method: 'POST',
    },
  );
  const data = await response.json();

  return data.success;
}

export async function sendContactForm(data: TContactForm) {
  const isValidToken = await isValidGoogleCaptcha(data.captchaToken);

  if (!isValidToken) {
    throw new Error('Invalid captcha token.');
  }
  return { success: true };
}

You have now successfully secured your Next.js forms! By implementing Google reCAPTCHA, you've built a powerful barrier against spam and malicious bots.

Key Takeaways:

  1. Never trust the client. A malicious user can bypass client-side checks entirely. The server-side API call to Google is non-negotiable.

  2. Your RECAPTCHA_SECRET_KEY should never be included in your frontend code or committed to public version control.

  3. Use v3 for a seamless user experience on most forms. Use v2 for pages where you need an explicit and visible security checkpoint, like logins or high-importance forms.

  4. Always let the user know if a submission fails, whether it's due to reCAPTCHA verification or something else.

Share this article

  • Twitter icon
  • Linkedin icon