Welcome to Rune 🎉!

Learn more

How to Implement Invisible Captcha with Next.js in 2022

Erik Mellum
Erik Mellum

Next.js Invisible Captcha in 2022

When I went to add Captcha to my site I was disappointed in the resources. I expected this to take about 10 minutes, and it ended taking me a few hours. I had to stitch together details from a variety of StackOverflows, Google documentation, Medium posts, and Github repo documentation. While this is fairly typical, I wanted to have a single place that contained all the useful information. This article focuses on invisible ReCaptcha. My examples are in Typescript. This article uses:

  • Next.js
  • React
  • Dotenv
  • Typescript

Overview

It's important to have a high level view of what we are doing, and how ReCaptcha works.

  1. We need a secret and key from ReCaptcha provider
  2. We need to install ReCaptcha script, we will use an npm package for this
  3. Add the installed package component into contact form
  4. Before sending our form data we generate a Captcha token. Include this token in the payload.
  5. Backend route will take this token and call a simple validation to tell us if the user is valid or not valid. If it's invalid we won't process the form submission.

Creating a reCaptcha Key

My reCaptcha kept returning false due to "invalid input secret". I was able to locate a StackOverflow that pointed out something opaque. Your keys must come from https://www.google.com/recaptcha/admin/create but not https://console.cloud.google.com/security/recaptcha. Are you kidding? Go ahead and create a secret and a key at the correct one.

Add key and secret to your .env file

If you don't have dotenv set up yet, go ahead and just hardcode these in for your testing. Once you get them working, find a nice article somewhere that teaches you how to use dotenv. Or just examine the source code in the next.js example with dotenv.

# CAPTCHA RECAPTCHA_KEY="key" RECAPTCHA_SECRET="secret"

Install react-google-captcha

Go install react-google-captcha. This package made installation of captcha more convenient than anything on the official documentation. Just use import ReCAPTCHA from "react-google-recaptcha";.

Add invisible ReCaptcha to your ContactForm component

This is as simple as it gets. Invisible ReCaptcha uses your submit button to generate the Captcha token. This is obviously much more convenient for a user than a ReCaptcha checkbox.

const recaptchaRef: any = React.createRef(); ... <ReCAPTCHA ref={recaptchaRef} size="invisible" sitekey={process.env.RECAPTCHA_KEY!} />

My submit button looks like this (but yours can look different). This is included as an example, but not strictly important for the tutorial.

<Button type="submit" className='mt-2 w-full inline-flex items-center justify-center px-6 py-3 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-sky-700 hover:bg-sky-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sky-600 sm:w-auto h-12' onClick={submit} text="Submit" state={state as any} />

Generate captcha token on form submission

I define my payload in the submit onClick handler. I included the whole function although only the

const captcha = await recaptchaRef.current.execute();

is relevant. Also, the documentation for react-google-recaptcha claimed that you should use executeAsync here, but it broke everything when I did that. This worked fine.

const submit = async (e: MouseEvent) => { e.preventDefault(); setState('Loading') try { const captcha = await recaptchaRef.current.execute(); await axios.post('/api/contact', { firstName, lastName, email, phone, subject, message, captcha, communicationMethod }) setState('Success') setEmail('') setModalOptions(successOptions) // window.grecaptcha.reset(); } catch (e: any) { setState('Error') setModalOptions({ title: 'Unable to send message.', description: e.response.data.error, action: 'Okay', icon: <div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100"> <ExclamationIcon className="h-6 w-6 text-red-600" aria-hidden="true" /> </div>, setOpen: setShowModal, }); } finally { setShowModal(true); } }

Add a helper function for validateCaptcha

This was one of the areas that I was most disappointed in the internet. I was thankful to have code examples to work off of, but as far as syntax goes they were basically trash (no offense internet). I also have two forms that I want to use Captcha on, so extracting captcha validation to a helper function is the right choice. Let's actually do this using modern es6 syntax:

import axios from 'axios' export default async function validateCaptcha(response_key: string) { const secret_key = process.env.RECAPTCHA_SECRET const url = `https://www.google.com/recaptcha/api/siteverify?secret=${secret_key}&response=${response_key}` try { const response = await axios.post(url) return response.data.success } catch (error) { console.error(`Error validating captcha: ${error}`) return false } }

Use our helper function in the backend to validate Captcha

Place this block right at the top of your submit handler in the api.

import { NextApiRequest, NextApiResponse } from 'next' import validateCaptcha from '../shared/components/validateCaptcha'; export default async function Contact(req: NextApiRequest, res: NextApiResponse) { const { captcha } = req.body; console.log('captcha', captcha); const validCaptcha = await validateCaptcha(captcha); if (!validCaptcha) { return res.status(422).json({ error: "Unprocessable request, Invalid captcha code.", }); } ... };

Wrapping up

Now you can test. If something goes wrong here is the list of resources that I used to create this article and troubleshoot when I had problems. Good luck hunting bots! Try using the chat widget if you want to talk about a more specific problem and who knows. Maybe a human will respond (no promises).

From the blog

Come see what we're up to. We are excited to add new content regularly. We write content about new technologies, new products, and new business opportunities.

Test Deployment

Erik Mellum

Erik Mellum

5m read

Do you embrace change or fear it?

Erik Mellum

Erik Mellum

5m read

Six reasons dropbox paper is better than notion

Erik Mellum

Erik Mellum

5m read

Why you should estimate development tasks not stories

Erik Mellum

Erik Mellum

10m read

How to grow your career as a software developer

Erik Mellum

Erik Mellum

10m read

Resource based planning for product and engineering teams

Erik Mellum

Erik Mellum

10m read

Tips and pitfalls of setting up a monorepo with Turbo, Nextjs, and Vercel

Erik Mellum

Erik Mellum

10m read

Best practices any team can use with their typescript projects

Erik Mellum

Erik Mellum

10m read

How to Implement Invisible Captcha with Next.js in 2022

Erik Mellum

Erik Mellum

10m read

Where Do Computer Viruses Come From?

Erik Mellum

Erik Mellum

3m read

Rune Launches in Chico

Erik Mellum

Erik Mellum

3m read

Why Do Computers Break?

Erik Mellum

Erik Mellum

5m read

Business IT

Would you like to hear more about our services or have a question? We'd love to hear from you. Please contact us at:

(530) 871-9422

Mon-Fri 8am to 5pm PST

business@rune.tech

Technical Support

Are you having problems with your technology? Give us a call or send us an email and we’ll get you back up and running.

(530) 871-9422

Mon-Sun 8am to 5pm PST

support@rune.tech