How to Use Next.js Server Actions to Handle Form Submissions

Siam Ahnaf
4 min readDec 28, 2023

Next.js Server Actions are asynchronous functions that run on the server, but can be invoked from both server-side and client-side components. They are useful for handling form submissions and data mutations in Next.js applications.

In this post, I will show you how to use Next.js Server Actions to create a simple contact form that sends an email to the site owner.

Prerequisites

To follow along, you will need:

  • Next.js 13 or later
  • A SendGrid account and API key
  • A basic understanding of Next.js and React

Setting up the project

First, create a new Next.js project using create-next-app:

npx create-next-app next-server-actions

Then, install @sendgrid/mail as a dependency:

npm install @sendgrid/mail

Next, create a .env.local file in the root of your project and add your SendGrid API key as an environment variable:

SENDGRID_API_KEY=your_sendgrid_api_key

Make sure to add .env.local to your .gitignore file to avoid exposing your API key.

Creating the contact form

Now, let’s create a simple contact form component that takes the user’s name, email, and message as inputs. We will use React Hooks to manage the state of the form fields and handle the form submission.

Create a file called ContactForm.js in the components folder and add the following code:

export default function ContactForm() {
// Handle form submission
const handleSubmit = async (formData) => {
"user server"

//Here need to write email sending functionality
}

return (
<form action={handleSubmit}>
<div>
<label htmlFor="name">Name</label>
<input
type="text"
id="name"
value={name}
/>
</div>
<div>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
value={email}
/>
</div>
<div>
<label htmlFor="message">Message</label>
<textarea
id="message"
value={message}
/>
</div>
<button type="submit">Send</button>
</form>
)
}

As you can see, I am using the user server decorative keyword for running submission function on server and here we can getformData by calling their ID. This is how we invoke the server action that we will define next.

Sending Email from the server action function

To create a server action, we need to use the use server directive at the top of a file or a function. This tells Next.js that the code should run only on the server and not on the client.

From previous function we can continue. First import dependency package

import sgMail from '@sendgrid/mail'

// Set the SendGrid API key
sgMail.setApiKey(process.env.SENDGRID_API_KEY)

And now complete the function for sending email-

// Handle form submission
const handleSubmit = async (formData) => {
"user server"
const rawFormData = {
to: formData.get("email"),
from: "your_sending_email",
subject: `New message from ${formData.get("name")}`,
text: formData.get("message"),
}

//Here need to write email sending functionality
try {
await sgMail.send(rawFormData)
// Send a success response
console.log("Email Send Successfully!")
} catch (error) {
// Send an error response
console.log("Something went wrong, please try again!")
}
}

Here, we are using the use server directive at the top of the function, which means that all the code from this function are server actions. This function gets the form data from the actions, creates an rawFormData object using the SendGrid mail library, and sends the email using the SendGrid API. It also sends a success or error response accordingly.

Here is the full component code

import sgMail from '@sendgrid/mail'

// Set the SendGrid API key
sgMail.setApiKey(process.env.SENDGRID_API_KEY)

export default function ContactForm() {
// State for the form fields

// State for the form status
const [status, setStatus] = useState('')

// Handle form submission
const handleSubmit = async (formData) => {
"user server"
const rawFormData = {
to: formData.get("email"),
from: "your_sending_email",
subject: `New message from ${formData.get("name")}`,
text: formData.get("message"),
}

//Here need to write email sending functionality
try {
await sgMail.send(rawFormData)
// Send a success response
console.log("Email Send Successfully!")
} catch (error) {
// Send an error response
console.log("Something went wrong, please try again!")
}
}

return (
<form action={handleSubmit}>
<div>
<label htmlFor="name">Name</label>
<input
type="text"
id="name"
value={name}
/>
</div>
<div>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
value={email}
/>
</div>
<div>
<label htmlFor="message">Message</label>
<textarea
id="message"
value={message}
/>
</div>
<button type="submit">Send</button>
</form>
)
}

Testing the contact form

To test the contact form, we need to import the ContactForm component in a page and render it. For example, we can create a file called contact.js in the pages folder and add the following code:

import ContactForm from '../components/ContactForm'

export default function ContactPage() {
return (
<div>
<h1>Contact Us</h1>
<ContactForm />
</div>
)
}

Now, if we run npm run dev and go to http://localhost:3000/contact, we should see the contact form on the page. We can fill in the form fields and click on the send button to submit the form. If everything works, we should see a success message on the page and receive an email in our inbox.

Conclusion

In this post, we learned how to use Next.js Server Actions to handle form submissions and data mutations on the server. We created a simple contact form that sends an email to the site owner using SendGrid.

Next.js Server Actions are a powerful feature that simplifies the development of Next.js applications by eliminating the need to create API endpoints. They also provide type safety, code reuse, and performance benefits.

If you want to learn more about Next.js Server Actions, you can check out the official documentation or some of the tutorials and articles on the topic.

--

--

Siam Ahnaf

I'm Siam Ahnaf, a passionate developer who loves to learn new things and create awesome projects. I enjoy working on both front-end and back-end development.