Understanding JSON → Next Server Action
The form's submit handler runs on the server.
What "use server" really does, why Server Actions replace half of the API route ceremony, and the validation pattern every action should have.
"use server" — the directive.
A function annotated with "use server" at its top, or defined inside a file with that directive at the top of the module, becomes a Server Action — an RPC-style callable that can be invoked from client components but runs on the server. Next.js compiles the function reference into a stable identifier; the client invocation becomes a POST to the framework's action endpoint.
Validate every input.
A Server Action is a public endpoint by construction — anyone can call it with any arguments. Zod (or any validator) inside the action validates the input before the handler runs. Skipping this is the most common Server Action mistake: the type system protects you at compile time, but a malicious client doesn't have to follow the types.
A worked action.
From { "title": "Hi", "body": "World" }: "use server";
import { z } from "zod";
import { revalidatePath } from "next/cache";
import { db } from "@/lib/db";
const schema = z.object({
title: z.string().min(1).max(200),
body: z.string().min(1).max(10000),
});
export async function createPost(formData: FormData) {
const parsed = schema.safeParse({
title: formData.get("title"),
body: formData.get("body"),
});
if (!parsed.success) {
return { error: parsed.error.flatten().fieldErrors };
}
await db.post.create({ data: parsed.data });
revalidatePath("/posts");
} Parse → validate → write → revalidate. The four steps of every well-formed action.
The four steps
parse → validate → write → revalidate
Every action follows this skeleton; bodies vary by domain.
schema.safeParse → db.write → revalidatePath
= A safe action
revalidatePath / revalidateTag.
After a mutation, the static or cached pages that show the affected data are stale. revalidatePath("/posts") tells Next to drop the cache for that path so the next render fetches fresh data. revalidateTag(tag) works the same way for tag-based cache invalidation. Forgetting these calls means the user sees their action complete but the list doesn't update until something else refreshes the page.
useActionState — progressive enhancement.
React 19's useActionState hook (formerly useFormState) lets a form work without JavaScript: the action runs server-side as a POST, the page re-renders with the result. With JavaScript, the hook updates state in place for instant feedback. Designing actions to return a typed result instead of just void enables this — return errors as data, not exceptions, so they flow through the state.
Server Actions vs API routes.
API routes still exist and remain right for public endpoints, webhooks, third- party integrations. Server Actions are right for first-party form submission and internal mutations where you control both ends. The boundary is "would another client ever want to call this?" — if yes, route; if no, action.