javascript 44 lines · 7 steps

How a Next.js Server Action validates a form

A server-side action parses form data with Zod, persists it, and redirects — returning structured errors when anything fails.

Explained by highlit
1'use server'
2 
3import { z } from 'zod'
4import { redirect } from 'next/navigation'
5import { db } from '@/lib/db'
6import { auth } from '@/lib/auth'
7 
8const ContactSchema = z.object({
9 name: z.string().min(1, 'Name is required').max(80),
10 email: z.string().email('Enter a valid email address'),
11 subject: z.string().min(3, 'Subject must be at least 3 characters'),
12 message: z.string().min(20, 'Message must be at least 20 characters'),
13})
14 
15export async function submitContact(prevState, formData) {
16 const parsed = ContactSchema.safeParse({
17 name: formData.get('name'),
18 email: formData.get('email'),
19 subject: formData.get('subject'),
20 message: formData.get('message'),
21 })
22 
23 if (!parsed.success) {
24 return {
25 errors: parsed.error.flatten().fieldErrors,
26 values: Object.fromEntries(formData),
27 }
28 }
29 
30 const session = await auth()
31 
32 try {
33 await db.message.create({
34 data: { ...parsed.data, userId: session?.user?.id ?? null },
35 })
36 } catch {
37 return {
38 errors: { _form: ['Something went wrong. Please try again.'] },
39 values: parsed.data,
40 }
41 }
42 
43 redirect('/contact/thanks')
44}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Server Actions let a form submit straight to server code without hand-written API routes.
  2. 2Returning a shaped state object with errors and values lets the UI re-render with feedback and preserved input.
  3. 3safeParse plus a try/catch cleanly separates validation failures from persistence failures.

Related explainers

Share this explainer

Here's the card — post it anywhere.

How a Next.js Server Action validates a form — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code