Skip to main content
Route Handlers vs Server Actions in Next.js: When Our Team Reaches for Each

Route Handlers vs Server Actions in Next.js: When Our Team Reaches for Each

July 19, 2026
Frontend Engineering
6 min read

Key takeaways

A Server Action is an async function marked `'use server'` that Next.js exposes as an internal POST endpoint we invoke directly from a Server or Client Component — usually a form action.

A Route Handler is a function exported from `app/api/**/route.ts` named after an HTTP method (GET, POST, and so on) that receives a Web Request and returns a Web Response.

We use a Server Action for mutations triggered from our own UI — it gives progressive enhancement and lets us call `revalidatePath`/`revalidateTag` in the same round trip.

We use a Route Handler for public APIs, webhooks, OAuth callbacks, cacheable GET endpoints, and streaming — anything a non-React client consumes over a stable URL.

Both are public endpoints. A Server Action is not private just because it has no visible URL; we authenticate, authorize, and validate inputs inside every action and handler.

Neither is needed to read data inside a Server Component — we query the data source directly and skip the extra HTTP hop.

What is a Server Action vs a Route Handler in Next.js?

A Server Action is an async function marked with the `'use server'` directive that Next.js compiles into an RPC-style endpoint. We import it and call it like a normal function — most often as the action of a form — and Next.js serializes the call over a POST request behind the scenes.

A Route Handler is a function exported from a `route.ts` file inside `app/`, named after the HTTP method it serves (GET, POST, PUT, PATCH, DELETE). It receives a standard Web Request and returns a standard Web Response, so we control the status code, headers, and body directly. It is the App Router replacement for `pages/api`.

When should we use a Server Action instead of a Route Handler?

We use a Server Action when the mutation is triggered from our own React UI and has no external consumer. A form that updates a profile, a button that deletes a row, a like toggle — these belong in Server Actions because the action lives next to the component that calls it and needs no public contract.

Server Actions give us two things a Route Handler does not: progressive enhancement, so a form submits and works even before JavaScript hydrates; and in-place cache revalidation, so calling `revalidateTag('profile')` at the end of the action re-renders the affected Server Components in the same round trip with no manual client refetch.

When should we use a Route Handler instead of a Server Action?

We use a Route Handler when something outside our own React tree needs to call the endpoint over a stable URL. Webhook receivers such as Stripe and GitHub, OAuth callbacks, mobile-app or third-party API consumers, cron jobs, and any GET endpoint we want cached at the CDN all require the explicit HTTP contract only a Route Handler gives.

Route Handlers are also the only option when we need raw request access or streaming. A Stripe webhook needs the unparsed request body to verify the signature; a Server-Sent Events feed or an LLM token stream needs to return a ReadableStream. Server Actions return a single serialized value and cannot do either.

How do Route Handlers and Server Actions compare?

How it is called: a Server Action is imported and called like a function; a Route Handler is invoked by an HTTP request to a URL.

HTTP method: a Server Action always runs as POST internally; a Route Handler can serve any method (GET, POST, PUT, PATCH, DELETE).

External clients: a Server Action has no stable public contract; a Route Handler exposes a stable URL and method for non-React clients.

Progressive enhancement: a Server Action form works without JavaScript; a Route Handler does not offer it.

Streaming and cacheable GET: a Route Handler supports both (ReadableStream and Cache-Control); a Server Action supports neither.

Best for: Server Actions fit mutations from our own UI; Route Handlers fit public APIs, webhooks, and streaming.

Do we need a Route Handler to fetch data for our own page?

No. Inside a Server Component we query the database or call the service directly — no Route Handler required. Wrapping the query in `app/api` and fetching it from the same server just adds an HTTP round trip and a second cache layer for no benefit.

We reserve Route Handler GET endpoints for data that a different client needs: a public JSON API, a response we want cached at the edge with Cache-Control, or an endpoint a mobile app calls.

Are Server Actions and Route Handlers both public endpoints?

Yes — both are publicly reachable POST endpoints, and this is the mistake we see most. A Server Action is not private just because it is called from a component and never shows its URL. Next.js compiles each action into an endpoint with an unguessable ID, but that ID can be invoked directly with a crafted request. The action ID is obscurity, not authorization.

We treat every Server Action and every Route Handler as an untrusted entry point: authenticate the caller, check they are allowed to perform the operation, and validate every input inside the function body. The client-side UI enforces nothing — a hidden form field or a disabled button stops no one.

FAQ

Q: Can a Server Action handle a GET request?

A: No. Server Actions always execute as POST. For a cacheable GET endpoint, use a Route Handler exporting a GET function.

Q: Should we use a Route Handler to fetch data for our own page?

A: No. In a Server Component, query the data source directly. A Route Handler adds an extra HTTP round trip and its own cache layer for no benefit when the page is already server-rendered.

Q: Are Server Actions secure by default because they have no visible URL?

A: No. Every Server Action compiles to a callable POST endpoint. Authenticate, authorize, and validate inputs inside the action itself; the generated ID is not a security boundary.

Q: Which one supports streaming responses?

A: Route Handlers. Return a ReadableStream for Server-Sent Events or LLM token streams. A Server Action returns one serialized result and cannot stream.

Q: Can a mobile app or third-party service call our Server Actions?

A: Not reliably. Server Action IDs are generated at build time and are not a stable public contract. Expose a Route Handler with a versioned URL for any external consumer.