Introduction
Before Server Actions, a simple form submission in Next.js meant: write a client component, wire up state, create an API route, call it with fetch, handle errors. It worked, but it was boilerplate-heavy.
Next.js 15 changes this. Server Actions let you write async functions that run on the server and call them directly from your JSX — no API route, no fetch, no useEffect. Here's how to build a contact form with them from scratch.
What is a Server Action?
A Server Action is an async function marked with the "use server" directive. When a form submits or a button is clicked, Next.js serialises the form data and sends it to the server — automatically. You never write the API endpoint yourself.
Step 1: Create the Server Action
Create a file called actions/contact.ts:
"use server";
import { db } from "@/lib/db";
export async function submitContact(formData: FormData) {
const name = formData.get("name") as string;
const email = formData.get("email") as string;
const message = formData.get("message") as string;
if (!name || !email || !message) {
throw new Error("All fields are required");
}
await db.contactMessage.create({
data: { name, email, message },
});
return { success: true };
}
Step 2: Build the Form Component
Now create your form. This is a Server Component — no "use client" needed:
import { submitContact } from "@/actions/contact";
export default function ContactForm() {
return (
);
}
Step 3: Add Pending State with useFormStatus
To show a loading state during submission, extract the button into a client component:
"use client";
import { useFormStatus } from "react-dom";
export function SubmitButton() {
const { pending } = useFormStatus();
return (
);
}
Replace the button in your form with <SubmitButton />. That's it — the rest of the form stays a Server Component.
Step 4: Handle Success & Error States
Use useActionState (new in React 19) to handle the action result:
"use client";
import { useActionState } from "react";
import { submitContact } from "@/actions/contact";
export default function ContactForm() {
const [state, action] = useActionState(submitContact, null);
return (
);
}
Why this matters
- No API route to maintain
- Works without JavaScript (progressive enhancement)
- Direct database access — no fetch round-trip
- Type-safe end-to-end with TypeScript
Conclusion
Server Actions are one of the most practical features in Next.js 15. For forms, mutations, and any server-side operation triggered by user interaction, they dramatically reduce the code you need to write. Give them a try on your next project.
