Lesson 54 of 80
A Server Action is a function that runs on the server but gets called directly from a form or a client component — no hand-written API route in between.
15 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Before Server Actions, "submit a form to the server" meant: write a POST handler as an API route, write client code to call fetch against it, serialize the form data yourself, handle the response, update state. All of that plumbing existed to connect two things that were, conceptually, already right next to each other in your codebase.
A Server Action collapses that plumbing. You mark a function "use server", and React (in frameworks like Next.js) generates the network call for you — the function can be passed straight to a form's action prop or called from an event handler, and it runs on the server even though you invoked it like a normal function from client code. You still write server-side logic (touch a database, check auth, whatever), you just stop hand-writing the wire protocol to reach it.
// app/actions.ts
"use server";
import { db } from "@/lib/db";
import { revalidatePath } from "next/cache";
export async function addComment(formData) {
const text = formData.get("text");
if (typeof text !== "string" || text.trim().length === 0) {
return { error: "Comment can't be empty." };
}
await db.comment.create({ data: { text } });
revalidatePath("/posts/1"); // tell Next.js this page's cached data is stale
}
// app/posts/[id]/CommentForm.jsx
import { addComment } from "@/app/actions";
export default function CommentForm() {
return (
<form action={addComment}>
<textarea name="text" required />
<button type="submit">Post comment</button>
</form>
);
}
No fetch, no onSubmit handler, no JSON body to construct. The form's action prop points straight at the server function; React wires up the network request behind the scenes. Crucially, this form still works with JavaScript disabled — <form action={fn}> degrades to a real HTML form submission, because that's fundamentally what it still is. That's the "progressive enhancement" people mean when they talk about Server Actions: the baseline behavior is the browser's native form submission, and JS just makes it smoother (no full page reload, pending states, optimistic UI) when it's available.
For calling one outside a form — say, a delete button — you use it directly in an event handler from a Client Component:
"use client";
import { deletePost } from "@/app/actions";
export default function DeleteButton({ id }) {
return <button onClick={() => deletePost(id)}>Delete</button>;
}
revalidatePath or revalidateTag yourself, or the UI will keep showing stale data after a successful mutation.Server Actions are a direct extension of the same boundary that separates Server Components from Client Components — a "use server" function is the mirror image of a "use client" component, marking the opposite crossing point of the same client-server line, so that code on either side can call across it without you hand-rolling the transport.