Server Actions vs API Routes vs Route Handlers in Next.js

The difference between Server Actions, Route Handlers, and API Routes, and which one to use for forms, webhooks, and external APIs.

7 min read

Server Actions, API Routes, and Route Handlers are three ways to run server-side code in Next.js. Server Actions are functions you call directly from components for mutations. Route Handlers are explicit HTTP endpoints in the App Router, and API Routes are the older Pages Router form of endpoint.

The main difference is the caller. Server Actions are called from your own components, while Route Handlers and API Routes are called by any HTTP client that knows the URL.

A Server Action has no route of its own that you name or publish, so there is no public path to document or version. That is a design difference, not a security boundary: an action still posts to the server and anyone who can reproduce that request can reach it.

FeatureServer ActionsRoute HandlersAPI Routes
Where definedA use server functionroute.ts file in appfile in pages/api
How it is calledForm or event handlerHTTP request to a URLHTTP request to a URL
HTTP methodsPOST onlyGET, POST, PUT, DELETE, and moreGET, POST, and more
RouterApp RouterApp RouterPages Router, legacy

Server Actions: functions called from components

A Server Action is an async function marked with the use server directive. You call it from a form or an event handler, and it always runs on the server.

index.tsindex.ts
// app/actions.ts
'use server'
 
export async function deletePost(formData: FormData) {
  const id = formData.get('id')?.toString()
  return { id }
}

The client sends a POST to the server with the arguments, and the server can return updated UI and data in one roundtrip. There is no public URL to manage, which is what makes actions feel lightweight.

Actions also sit next to your existing authentication and data access code, so a mutation and its permission check live in the same module. You still have to write that check inside the action, because rendering the form only for signed-in users does not stop a direct POST. See Server Actions in Next.js for the full picture.

Route Handlers: explicit endpoints

A Route Handler lives in a route.ts file and exports functions named after HTTP methods. It is the App Router way to build a real API with a URL you control.

index.tsindex.ts
// app/api/posts/route.ts
export async function GET() {
  return Response.json({ posts: [] })
}
 
export async function POST(request: Request) {
  const body = await request.json()
  return Response.json({ id: 1, ...body })
}

Each export answers one HTTP method on /api/posts. A route.ts file can export GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS.

Reach for a Route Handler when you need a stable URL for an outside client, or control over status codes, headers, and streaming responses. That combination is what makes it the right choice for webhooks and external integrations.

One default catches people out: since Next.js 15, GET handlers are dynamic rather than static, so a handler runs per request unless you deliberately cache inside it. See Route Handlers in Next.js for the methods and request helpers.

API Routes: the Pages Router predecessor

API Routes are the Pages Router convention that Route Handlers replaced. They live under pages/api and use a single default export with a request and response pair.

index.tsindex.ts
// pages/api/posts.ts
import type { NextApiRequest, NextApiResponse } from 'next'
 
export default function handler(req: NextApiRequest, res: NextApiResponse) {
  res.status(200).json({ posts: [] })
}

This only exists in Pages Router apps. In the App Router, use a Route Handler instead.

If you are migrating, each API Route maps naturally to a Route Handler, so you can convert them file by file. API Routes still work, but they are not the current convention.

The conversion follows a fixed shape. One default export becomes one named export per HTTP method, the req and res pair becomes a standard Request argument, and writing JSON onto the response object becomes a returned Response.

Anything that relied on streaming through the res object, or on the bodyParser config, has to be rewritten around Web APIs.

Which one should you use

Choose by who is calling your code and what the call does.

  • Use a Server Action when your own component submits a form or triggers a mutation.
  • Use a Route Handler when an external system, a webhook, or another client needs a stable URL.
  • Avoid API Routes in new App Router code and migrate them to Route Handlers.

A common mistake is building a Route Handler for a form your own app could handle more simply, or assuming a Server Action is private because it has no URL.

The rule of thumb is the caller. A third party sending you data, such as a payment provider calling back, needs a Route Handler, while your own UI changing data is a Server Action.

See using Next.js as a backend for the endpoint patterns that scale.

Rune AI

Rune AI

Key Insights

  • Server Actions are functions called from components, limited to POST.
  • Route Handlers are explicit endpoints in the App Router using route.ts.
  • API Routes are the legacy Pages Router endpoints under pages/api.
  • Choose a Server Action for app mutations and a Route Handler for external callers.
  • Migrate Pages Router API Routes to Route Handlers.
RunePowered by Rune AI

Frequently Asked Questions

Are API Routes and Route Handlers the same thing?

No. API Routes are the Pages Router convention that lives in pages/api. Route Handlers are their App Router replacement, defined in route.ts files with standard Request and Response objects.

Can a Server Action handle GET requests?

No. Server Actions can only be invoked with POST. Use a Route Handler when you need GET or other HTTP methods.

Should I use a Route Handler for every mutation?

No. If the mutation happens from a component in your own app, a Server Action is simpler and returns updated UI in one roundtrip. Route Handlers suit webhooks and external API clients.

Conclusion

Server Actions, Route Handlers, and API Routes all run server code, but they serve different callers. Use Server Actions for mutations from your own components, Route Handlers for explicit endpoints and webhooks, and migrate API Routes to Route Handlers.