In this guide, I'll show how you can easily add a contact form to a Next.js application when using the pages router.
I've also published a guide for adding a contact form to a Next.js app using the app router.
Here's what we'll build:
- A simple
<ContactForm />
component - An API route to send the form data to an email address of our choice
That's all we need to end up with something like this:
If you'd like to jump straight into the code, feel free to check out the repository on GitHub.
Let's get started!
1. Create a ContactForm component
The <ContactForm />
component needs to take a name, email and message as input, and send this data to an API route when the form is submitted.
We won't be using any fancy form libraries for this example. We'll rely on the native form elements and their built-in validation instead.
components/ContactForm.tsx
import React, { useState } from 'react'function ContactForm() {const [loading, setLoading] = useState(false)const [successMessage, setSuccessMessage] = useState('')const onSubmit = async (e: React.FormEvent) => {// Prevent the form from submitting the traditional waye.preventDefault()// Don't submit twiceif (loading) {return}// 👇 A nice little track to get all the form values as an objectconst form = e.target as HTMLFormElementconst formValues = Object.fromEntries(new FormData(form).entries())setLoading(true)setSuccessMessage('')try {await fetch('/api/contact', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify(formValues),}).then((response) => {if (!response.ok) {throw new Error(`HTTP error! Status: ${response.status}`)}return response.json()})setLoading(false)setSuccessMessage('Thank you for contacting us!')// Reset the form values after a successful submissionform.reset()} catch (err) {console.error(err)alert('An error occurred while sending your message...')setLoading(false)}}return (<form onSubmit={onSubmit}><label><span>Name</span><input type="text" name="name" required /></label><label><span>Email</span><input type="email" name="email" required /></label><label><span>Message</span><textarea name="message" required /></label><button disabled={loading} type="submit">Send message!</button>{successMessage && <p>{successMessage}</p>}</form>)}export default ContactForm
2. Send an email from a Next.js API route
Now that we have our <ContactForm />
component built, let's implement the API route that will accept the form data and send an email to us.
There are many transactional email services but I like using Mailgun because they have a generous free tier.
After you've got that setup, you'll need to get the following environment variables and save them to your .env
file:
CONTACT_FORM_FROM_EMAIL
: The email address that the contact form submissions will be sent fromCONTACT_FORM_TO_EMAIL
: The email address that the contact form submissions will be sent toMAILGUN_DOMAIN
: The domain that you've set up in MailgunMAILGUN_API_KEY
: The API key that you've set up in Mailgun
We'll also need two packages: mailgun.js
and form-data
. You can install them by running:
$ yarn add mailgun.js form-data
Then go ahead and add the following API route to your Next.js application:
pages/api/contact.ts
import type { NextApiRequest, NextApiResponse } from 'next'import Mailgun from 'mailgun.js'import FormData from 'form-data'const CONTACT_FORM_FROM_EMAIL = process.env.CONTACT_FORM_FROM_EMAIL as stringconst CONTACT_FORM_TO_EMAIL = process.env.CONTACT_FORM_TO_EMAIL as stringconst MAILGUN_DOMAIN = process.env.MAILGUN_DOMAIN as stringconst MAILGUN_API_KEY = process.env.MAILGUN_API_KEY as stringconst mailgun = new Mailgun(FormData)const mg = mailgun.client({username: 'api',key: MAILGUN_API_KEY,// This needs to be customized based on your Mailgun regionurl: 'https://api.eu.mailgun.net',})async function contact(req: NextApiRequest, res: NextApiResponse<string | void>) {// Only allow post requestsif (req.method !== 'POST') {return res.status(404).end()}// Get the form data from the request bodyconst { name, email, message } = req.body// Put together the email textconst text = ['From: ' + name + '<' + email + '>\n', message].join('\n')// Send the email using Mailgunawait mg.messages.create(MAILGUN_DOMAIN, {subject: 'New contact form submission',from: CONTACT_FORM_FROM_EMAIL,to: CONTACT_FORM_TO_EMAIL,text,'h:Reply-To': email,})// Send a 200 OK responseres.status(200).json({ status: 'ok' } as any)}export default contact
That's it
That was all that we need. I really like that Next.js makes it so easy to sprinkle in some server-side code for things like this.
I know they're pushing to do more and more backend stuff with Next.js with server actions but I haven't been able to get convinced to use it yet for full-blown APIs. But for simple things like contact form submissions, Next.js API routes are perfect.