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>
);
}
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.