All posts
8 min read

Building a Contact Form in Next.js with Server Actions

How to build a working contact form in Next.js using Server Actions — no API route needed. Includes validation, loading states, and email delivery with Resend.

The old way vs. Server Actions

Traditionally, building a contact form in Next.js meant creating a form component, writing a separate API route in src/app/api/, making a fetch() call from the client, and handling the response. That's four files and a lot of boilerplate for something simple.

Server Actions let you define the server-side logic directly in your component file. The form submits to a function that runs on the server — no API route, no fetch(), no JSON serialization. It's the cleanest way to handle form submissions in Next.js.

Step 1: Create the form component

Start with a standard React form. Name, email, message — the basics. Use Tailwind for styling: rounded inputs with border-zinc-800 bg-zinc-900, focus states with focus:border-purple-500, and a submit button with a loading state.

Add 'use client' at the top since we need useState for the form status. The form itself will call a Server Action, but the UI state management happens on the client.

Step 2: Write the Server Action

Create a function marked with 'use server'. This function receives FormData, extracts the fields, validates them, and sends the email. It runs entirely on the server — your email API key never touches the browser.

For validation, check that all fields exist and the email format is valid. Return an object with either a success message or an error. The client component uses this return value to update the UI.

Step 3: Send the email

We use Resend for email delivery because it's the simplest option for developers. Install the resend package, get an API key, and call resend.emails.send() with the form data. That's three lines of code.

Set up a verified domain for production — sending from a custom domain instead of onboarding@resend.dev dramatically improves deliverability. In development, Resend lets you send to your own email address without domain verification.

Step 4: Handle loading and success states

Use useActionState (React 19) or a simple useState pattern to track whether the form is idle, submitting, or complete. Disable the submit button during submission and show a success message when the email is sent.

A good success state replaces the entire form with a confirmation message. Don't just show a toast — replace the form so users can't accidentally submit twice.

The complete pattern

The beauty of this approach is its simplicity. One file for the form component, one function for the server logic, and the Resend SDK for email delivery. No API routes, no fetch calls, no middleware.

This same pattern works for newsletter signups, project inquiry forms, feedback widgets — any form that needs to do something on the server. We use it on every project we build. If you need a working example, our free templates include contact forms built exactly this way.


Need a custom version?

We build it for you.

Custom web applications, business systems, and marketing sites — built to your exact specifications. Projects starting from $2K.