Skip to the solution
MediumFrontend5 min read

How do Server Actions differ from Route Handlers in Next.js?

737 views

The problem

Question presented to candidate: "When would you write a Route Handler instead of a Server Action? Give me concrete cases for each."

What a strong answer should cover:

  • Server Actions: functions you call from your own React UI; Next.js generates the endpoint, uses POST, handles serialization and revalidation in the same round trip.
  • Route Handlers: route.ts files exporting HTTP methods; you design the URL, method, status codes and payloads for any client.
  • Use actions for mutations triggered by your own forms and buttons.
  • Use Route Handlers for webhooks, public or mobile APIs, file downloads, GET endpoints that others call, and anything that needs custom headers or streaming responses.
  • Both run on the server and both need their own authentication.

Clarifying questions expected:

  • "Who calls this: our React UI or someone else?" β€” the main deciding question.
  • "Does the caller need a stable URL and method?" β€” only Route Handlers give that.

Code / implementation expected: Yes β€” the same mutation as an action and as a Route Handler.

  • nextjs
  • server-actions
  • route-handlers
  • api

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

5 min read Β· 8 sections

Read the code

The same mutation both ways, sharing one function
// lib/comments.ts
import "server-only";
export async function addComment(userId: string, postId: string, text: string) {
  return db.comment.create({ data: { userId, postId, text } });
}

// app/posts/[id]/actions.ts  -> for our own React UI
"use server";
import { revalidatePath } from "next/cache";
export async function addCommentAction(postId: string, formData: FormData) {
  const user = await requireUser();
  await addComment(user.id, postId, String(formData.get("text")));
  revalidatePath(`/posts/${postId}`);
}

// app/api/posts/[id]/comments/route.ts  -> for the mobile app
export async function POST(req: Request, ctx: { params: Promise<{ id: string }> }) {
  const user = await requireApiUser(req);          // token auth for external clients
  const { id } = await ctx.params;
  const { text } = await req.json();
  return Response.json(await addComment(user.id, id, text), { status: 201 });
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 53 of 129 in Next.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Next.js questions
Up nextMedium

What is error.js in Next.js and how does error handling work in the App Router?

Continue