This Gatsby post was written for Gatsby 3 and is no longer maintained. If you would like to try out SvelteKit or Astro, check out the maintained posts which are based on those. Astro lets you continue to use React but also with partial hydration which can be used to provide an enhanced user experience. SvelteKit offers server side rendering as well as static site generation. See the post on how performance improved switching from Gatsby to Astro for more background.
In this Gatsby Cloud Functions reCAPTCHA project, we learn all about a brand-new feature in Gatsby. That new feature is Gatsby Cloud Functions. Gatsby Cloud Functions let you code serverless (also known as lambda) functions right into your static Gatsby site. You might use them for processing form data or creating documents in your database via an API. Whatever you use them for, you will not have to worry about provisioning servers, maintaining them or even ensuring you have enough capacity to handle busy periods. All of that is taken out of your hands.
I got early access to Gatsby functions last month and set about building a demo site straight away. It is a static Gatsby MDX blog, which lets users leave comments on blog posts . Using cloud functions, the site automatically triggers a rebuild, to include fresh comments in the site's static content. All of that functionality was powered by serverless functions.
Today we'll look at something a little less ambitious, though still exciting. We are going to pair up Gatsby Cloud functions with the accessible Google reCAPTCHA. Read on to see how that is different from the annoying Captcha challenges of the past. We will bundle those features into a contact form for a Gatsby MDX blog.
If that sounds exciting, why don't we crack on? Before that though, as promised, we need to learn all about reCAPTCHA.
We have all probably done a CAPTCHA challenge at some point. You fill out a huge form, get to the bottom and have to run through a series of (seemingly) endless challenges! What's worse is that traditional CAPTCHAs can be inaccessible for partially sighted users . With reCAPTCHA, as users interact with the website, Google runs code behind the scenes, employing machine learning algorithms to determine if the interactions are typical of a human user or a bot. On form submission we can ask reCAPTCHA to generate a score from those user interactions giving us a metric to decide on whether we should perform some additional verification.
With that explained, let's build something interesting which you can use on your own site. We will look in more detail at the reCAPTCHA verification process as we go along. Most importantly, it will demonstrate Gatsby Cloud functions.
We will take a Gatsby v3 MDX starter blog and add a contact form using Formik. The submitted data will then be sent to our serverless function. Subsequently, the function will email us using SMTP to let us know someone has a message for us. We use SMTP to increase compatibility with whichever service you are already using, be it Mailtrain, Migadu, Postmark , Sendgrid or some other service. Wes Bos recommends using SMTP over a service's API in case you have to switch service at short notice. If you are using SMTP, it is just a case of swapping out credentials (rather than having to learn another API).
Now we know what we're doing, let's get a wriggle on! The first step will be to log into Google to generate reCAPTCHA API keys.
Firstly, head over to Google to get API keys — at www.google.com/u/1/recaptcha/admin/create . We need to get reCAPTCHA v3 credentials. This includes a _site key_ together with a _secret
key_. So once you log in, enter a label for your site. Be sure to choose reCAPTCHA v3 and then enter the domains you need. You might want to add
localhost as a domain now and then remove it once you are satisfied things are working. Make a note of
the two generated keys, we will need them in the next step.
Let's spin up a site to add those new keys to. We will use Gatsby Starter Climate, which is a shell MDX blog site with a contact page . At the command line type:
The site has some environment variables which we need to define (we will add the new reCAPTCHA API keys shortly). Following best practices, these are not stored in the repo. Instead, you just need to copy the example file, which is in the repo:
The last command spins up the site. You will see, as it stands, the blog is fully functional (open
the site by going to
localhost:8000). Before going on, take a look
through the repo in your favourite code editor. Once you are comfortable with how it all fits
together, we’ll carry on.
If you're ready, let's add those environment variables. Edit the
.env.production files, adding the following two lines to
We'll use some boilerplate code to speed things up in creating an accessible form. Let's start by
installing the extra packages we will need:
lodash.isobject (used in
FormikErrorFocus component, below) and
axios for posting the form data to the cloud function.
Let's create a basic contact form component. Create a component file at
src/components/ContactForm.jsx and add the following content:
src/components/ContactForm.jsx — click to expand code.
This won't work yet; we need to add a couple of imports first. I won't go into much detail explaining what we have here, since our main focus is Gatsby Cloud functions. Drop a comment below if you would like to see a separate post on creating accessible forms in Gatsby. Still, I should mention we have included a field just for bots! This is a trick recommended by Netlify . The field is not visible in the browser and not announced by screen readers. It is present in the DOM, though. It is pretty difficult for a human accidentally to find the field and enter a value. For that reason, whenever there is a value submitted for this field, it is quite safe to assume we are dealing with a bot. More on this when we create our cloud function code.
Next, we define accessible input fields. We do this using reusable components. Create a file for
our input component at
src/components/InputField.jsx. Give it the
src/components/InputField.jsx — click to expand code.
With our input components defined, let's touch them up a little with a spot of CSS. Create a style
src/components/InputField.module.scss and paste this
minimal styling into it:
Let's not leave the contact form component out:
src/components/ContactForm.module.scss — click to expand code.
Finally, we add some boilerplate code to improve user experience. This is just to help users out when there are form errors. For example, if the user submits the form, forgetting to enter their email, the email field becomes focused and highlighted, so they know exactly where the error is.
src/components/FormikErrorFocus.jsx — click to expand code.
Once again, I won't talk much about this, but drop a comment below if you have a question or two!
We're moving along quite quickly now! Next thing we'll look at is the client side Google Captcha
API call. The way reCAPTCHA works, we need to make two calls to the API. The first, on the client
side, is to get a token which identifies the user session. That token is valid for two minutes. As
soon as we have the token, we will send it to the cloud function, so it can be used by the second
API call, before it expires. We'll build up the code to implement this now. Edit the
src/components/ContactForm.jsx — click to expand code.
We've done a few things here, so let's go through them one by one. First, we mentioned before that
reCAPTCHA observes user behaviour up to the point they submit the form. That is done via the
script we load in lines
66. We use React Helmet to place the
script in the page’s HTML head section. Next, when the user clicks the submit button, Formik
handleSubmit function, we call the
grecaptcha function, which, in turn, is defined in the script we just added via Helmet. This function
contacts Google and fetches us a unique client token. Finally,
submitData. For now, that final function just prints the
form data to the console. Let's change that next!
Finally, for the client, we need to post the form data to our Gatsby Cloud function. To use Gatsby
Cloud functions, we need to create an
api folder under
src. We will soon see how this is done. For now let's just finish the client side by posting the
comment. Update the
src/components/ContactForm.jsx — click to expand code.
This is just a basic API call using axios. There is a fair bit of error handling code here, which is helpful for debugging. This is mostly boilerplate, nonetheless, let me know if you think it needs more explanation. You can also check the axios documentation for help . With the client side wrapped up, let's jump to the highlight of this project; the Gatsby Cloud function.
If you are using Gatsby 3.7 or newer, there is no additional config to start using Gatsby Cloud
functions. Let's actually define our serverless function, then. On the client side, we post data
/api/submit-message route. So we need to create a file
containing the cloud function, in our project, with the matching path
src/api/submit-message.js. Let's do that now:
src/api/submit-message.js — click to expand code.
We probably completed the most technically challenging part of our project when we set up the contact form. In common with Netlify functions , Gatsby functions are fairly easy to implement, though the syntax differs slightly between the two services. One pro for Gatsby cloud functions is that it is easier to test functions locally, on the development server. We'll see this in a moment, but first let's go through the code we have just added.
So the main function is the handler. This is what gets called when a user hits the API. In lines
11 of our serverless function, we see how
we can check the user called our handler with the expected method (that is
POST as opposed to
GET etc.). Normally, we would want to revalidate
all input data, within the cloud function. For the sake of brevity, we won't go into that here,
though you can use similar functions to the ones used on the client side. In lines
33 we see how we can send different response codes back to the client browser. Apart from the
response code, we can respond to the user's API call with an HTTP body. The body can be JSON data
or a string:
We're going for string responses in our project. We just include JSON for future reference. There's not much left to do now. Next we will flesh out the code for the reCAPTCHA server side response. After that, we fill out the SMTP email function, and then we are all set for testing!
This is the second call to the reCAPTCHA API. Our Gatsby Cloud functions reCAPTCHA call will send the reCAPTCHA token we got in the client. The Google server will respond with a score. That score is on the scale zero to one, with zero indicating it is very likely the user is a bot.
If that's all clear, let's press on and fill out the function body. Edit
In a production application, you would probably want to implement a method for additional user validation if the reCAPTCHA score is below a chosen threshold. Here, we will just pass the score through on the email we send ourselves with the user's message. Talking of email, let's now finish up by writing that final function.
We're now ready to write out the
sendEmail function. We mentioned
earlier that we will use SMTP rather than any particular API to future-proof our code. We will
need SMTP credentials to be able to send email from the Gatsby Cloud function. As before, in line
with best-practise, we define those credentials in environment variables. Update
.dev.production with this additional variables:
The precise values will depend on your preferred service. The documentation for Sendgrid and Mailgun is linked. If you don't yet have an SMTP server to test with, get free SMTP test credentials from Ethereal .
Moving on swiftly, we will use nodemailer to send email from the lambda function, so let's install it:
Our final step here is to import nodemailer and to fill out the
The email we send here is a basic, plaintext email containing the bare essentials. If you are setting this up for a client, you might consider using an HTML email and formatting it nicely!
For the sake of clarity, let's have an overview of the message submission process:
- The user submits the form on our website.
The information from the form is then sent by this Gatsby Cloud function. The email address
which the function sends from will be whatever you chose for the value
SMTP_SENDERin the environment variables. You will need to have your service configured to send email from that address for this to work.
Whenever a user submits a message on the contact form, you will receive an email containing the
message details. The email is sent to whichever email address you set
CONTACT_EMAILto in your environment variables.
Hope that makes sense, but let me know if that needs more explanation.
Well done for making it this far! Let's wrap up by sending a test comment from the front end.
You can do this on your local development server, without the need to push your repo to your
hosting service. Go to
localhost:8000/contact/ and send a test
You can see the full code for this Gatsby Cloud Functions reCAPTCHA example on the Rodney Lab GitHub repo.
In this post, we learned:
- how to use Gatsby functions;
- using reCAPTCHA to check for bots; and
- how to send SMTP email using a cloud function.
I hope you have found this valuable and worth the time you invested. Let me know if there is anything in the post that I can improve on, for anyone else creating this project. You can leave a comment below, @ me on Twitter or try one of the other contact methods listed below.
As I say, I hope you enjoyed following along and creating this project as well as learned something new. I also hope you will use this code in your own projects. I would love to hear what you are building with cloud functions as well as reCAPTCHA. Perhaps you are de-googling and will opt for HCaptcha instead of reCAPTCHA. Hopefully you can leverage something from this post in that endeavour. Let me know about your journey as well as the differences in using the two APIs. Read on to find ways to get in touch, further below. If you have found this post useful, even though you can only afford even a tiny contribution, please consider supporting me through Buy me a Coffee.
Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter and also askRodney on Telegram . Also, see further ways to get in touch with Rodney Lab. I post regularly on SvelteKit as well as Gatsby JS among other topics. Also, subscribe to the newsletter to keep up-to-date with our latest projects.