Goodbye API Routes: Mastering Server Actions in Next.js 14
The Old Way vs. The New Way
Before Next.js 14, handling a simple form submission meant creating a separate file in pages/api/submit.ts, handling the request method, parsing the body, and managing manual fetch calls on the client.
With Server Actions, this complexity vanishes. We can define backend logic directly alongside our UI components.
A Real-World Example
Here is how I implemented the "Contact Me" form on this very portfolio using Server Actions and Zod for validation.
// actions/send-email.ts
"use server";
import { z } from "zod";
const schema = z.object({
email: z.string().email(),
message: z.string().min(10),
});
export async function sendEmail(formData: FormData) {
const data = Object.fromEntries(formData.entries());
// 1. Validate on the server (never trust the client)
const result = schema.safeParse(data);
if (!result.success) {
return { error: "Invalid input" };
}
// 2. Mutate Data (DB call or Email service)
try {
await db.messages.create({ data: result.data });
return { success: true };
} catch (e) {
return { error: "Database failure" };
}
}
Why This Matters
Type Safety: The arguments and return types are inferred automatically.
Progressive Enhancement: Server actions work even if JavaScript is disabled on the client (mostly).
No API Boilerplate: I deleted 12 API files from my project after migrating to this pattern.
Conclusion
Server Actions aren't just "syntactic sugar." They represent a fundamental shift in how we think about the boundary between client and server.