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.
| Feature | Server Actions | Route Handlers | API Routes |
|---|---|---|---|
| Where defined | A use server function | route.ts file in app | file in pages/api |
| How it is called | Form or event handler | HTTP request to a URL | HTTP request to a URL |
| HTTP methods | POST only | GET, POST, PUT, DELETE, and more | GET, POST, and more |
| Router | App Router | App Router | Pages 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.
// 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.
// 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.
// 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
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.
Frequently Asked Questions
Are API Routes and Route Handlers the same thing?
Can a Server Action handle GET requests?
Should I use a Route Handler for every mutation?
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.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.