Search code examples
javascriptnext.jsrecaptcha

How can i implement ReCaptcha into a Nextjs contact form?


So im working on a portfolio website where i have a contact form implemented for people to reach me on. I was able to connect it to SendGrid for the email transfering, but now im worried about spam. I looked everywhere online for a good solution, but i just cant seem to get ReCaptcha working in my project :(. Can someone pls help me on this and explain what i could do better?

I have tried ReCaptcha v2 and v3, but both no good. The client side programming is working fine like its supposed to, but the problem is that i cant seem to get it working in the server side. Down below is my code.

import React, { useState, useRef } from "react";
import ReCAPTCHA from "react-google-recaptcha";

export default function Contact() {
  const [name, setName] = useState("");

  async function handleSubmit(e) {
    const formData = {};
    Array.from(e.currentTarget.elements).forEach((field) => {
      if (!field.name) return;
      formData[field.name] = field.value;
    });
    fetch("/api/email", {
      method: "post",
      body: JSON.stringify(formData),
    });

    alert(
      `Bedankt voor het sturen van een bericht ${name}! Ik zal spoedig contact met je opnemen.`
    );
  }

  return (
    <main className="bg-gradient-to-b from-black to-[#434343]" id="contact">
      <section>
        <div className="flex mt-5">
          <h2 className="mx-auto text-3xl font-bold">Contact</h2>
        </div>
        <div className="flex mt-5">
          <p className="mx-auto text-center text-gray-300">
            Neem gerust contact op voor vragen of andere verzoeken!
          </p>
        </div>
        <div className="grid ld:grid-cols-2 esd:grid-cols-1 sd:grid-cols-1">
          <div className="flex">
            <img
              src="/contact/mail.png"
              alt=""
              className="w-[75%] mx-auto my-auto"
            />
          </div>
          <div className="flex mx-32 esd:mx-5 mb-10">
            <form
              method="post"
              onSubmit={handleSubmit}
              className="mx-auto my-auto space-y-5"
            >
              <input
                type="text"
                name="naam"
                value={name}
                required
                onChange={(e) => setName(e.target.value)}
                className="w-full rounded-full p-2 border bg-white text-black"
                placeholder="Naam"
              />
              <input
                type="email"
                name="email"
                required
                className="w-full rounded-full p-2 border bg-white text-black"
                placeholder="Wat is je email?"
              />
              <textarea
                placeholder="Je vraag of verzoek..."
                name="bericht"
                required
                className="w-full h-40 rounded p-2 border bg-white text-black"
                maxLength={300}
              ></textarea>
              <input
                type="submit"
                className="bg-gradient-to-br from-orange-400 to-[#4ECDC4] p-2 rounded cursor-pointer"
              />
            </form>
          </div>
        </div>
      </section>
    </main>
  );
}

Solution

  • As you have problem on Backend side. So First, make sure you have the following environment variables in your .env.local file:

    NEXT_PUBLIC_RECAPTCHA_SITE_KEY=<your-recaptcha-site-key>
    RECAPTCHA_SECRET=<your-recaptcha-secret-key>
    

    Create a new API route in your Next.js project by creating a new file called validateRecaptcha.js inside the pages/api folder. In this file, add a function to validate the reCAPTCHA response key with the secret key. This can be done using the fetch function to make a POST request to the reCAPTCHA API:

    // pages/api/validateRecaptcha.js
    export default async function handler(req, res) {
      const { recaptchaResponse } = req.body;
      const secretKey = process.env.RECAPTCHA_SECRET;
    
      const response = await fetch(
        `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${recaptchaResponse}`,
        {
          method: "POST",
        }
      );
      const data = await response.json();
    
      if (data.success) {
        res.status(200).json({ success: true });
      } else {
        res.status(400).json({ success: false });
      }
    }
    
    

    Update your handleSubmit function in your Contact component to send the reCAPTCHA response key to the API route and check the response:

    async function handleSubmit(e) {
      e.preventDefault();
      const recaptchaResponse = await recaptchaRef.current.executeAsync();
      recaptchaRef.current.reset();
    
      // ...continue with your existing form data processing
    
      const response = await fetch("/api/validateRecaptcha", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ recaptchaResponse }),
      });
    
      if (response.ok) {
        // reCAPTCHA validation passed
        fetch("/api/email", {
          method: "post",
          body: JSON.stringify(formData),
        });
        alert(
          `Bedankt voor het sturen van een bericht \${name}! Ik zal spoedig contact met je opnemen.`
        );
      } else {
        // reCAPTCHA validation failed
        alert("reCAPTCHA validation failed. Please try again.");
      }
    }
    

    Finally, make sure you have a reference to the ReCAPTCHA component in your Contact component and that the sitekey prop is set to your NEXT_PUBLIC_RECAPTCHA_SITE_KEY environment variable:

    const recaptchaRef = useRef();
    
    // ...
    
    <ReCAPTCHA
      ref={recaptchaRef}
      sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}
    />
    

    With these changes, your server-side reCAPTCHA validation should now work properly. When the form is submitted, the reCAPTCHA response key will be sent to the /api/validateRecaptcha API route, which will validate it using the reCAPTCHA secret key. If the validation is successful, the form data will be sent to the /api/email API route as before.

    This solution uses reCAPTCHA v2 Invisible, but the same approach can be applied to reCAPTCHA v3 with some minor modifications.