In a previous guide, I demonstrated how to easily add a contact form to a Next.js application with the pages router.
Today, we'll build the same contact form but use the Next.js app router for the API route.
Just like last time, we need:
- A simple
<ContactForm />
component - An API route to send the form data to an email address of our choice
And we'll 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
We'll take the ContactForm component from the other post. We just need to make sure to add 'use client'
to the top to mark it as a client component.
components/ContactForm.tsx
'use client'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
To implement the API route for sending the email using the Next.js app router, we'll once again rely on Mailgun for sending the email.
Once again, you'll need to add the following environment variables:
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
Also, make sure to install the mailgun.js
and form-data
packages:
$ yarn add mailgun.js form-data
The actual route handler is very similar to the one we built for the pages router. The difference lies mostly in how we handle the request
and response
.
I've highlighted the main differences to the previous pages router-based implementation below:
app/api/contact/route.ts
import Mailgun from 'mailgun.js'import FormData from 'form-data'import { NextRequest, NextResponse } from 'next/server'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',})// Export the POST handlerexport async function POST(request: NextRequest) {// Get the form data from the request bodyconst { name, email, message } = await request.json()// 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 responsereturn NextResponse.json({ status: 'ok' })}
That's it
It's quite straight-forward to build this using the app router. I do like the new folder structure of the app
directory and the fact that you can directly export function POST () {}
from a route file.
I hope this contact form solution comes in handy for you!