# Route Handlers vs Server Actions: which one to use

Server Action for form mutations, Route Handler for everything else. The caller decides: comparison table, working code and real limits.

> Page: https://ui.eneawork.it/blog/nextjs-route-handlers-vs-server-actions
> Markdown: https://ui.eneawork.it/blog/nextjs-route-handlers-vs-server-actions.md

*2026-11-01*

Usa una Server Action per una mutazione che parte da un form del tuo sito; usa un Route Handler quando chi chiama non è un componente React. La scelta dipende dal chiamante, non dal gusto personale.

## Il chiamante decide

Un Route Handler è un file `route.ts` che esporta una funzione per ogni metodo HTTP che accetti. Riceve una `Request` standard e restituisce una `Response`: nessun tipo proprietario, nessuna astrazione.

// app/api/ordini/route.ts import { NextResponse } from "next/server"; import { z } from "zod"; import { createOrder } from "@/lib/orders"; const schema = z.object({ sku: z.string().min(1), qty: z.number().int().positive(), }); export async function POST(request: Request) { const body = await request.json(); const parsed = schema.safeParse(body); if (!parsed.success) { return NextResponse.json({ error: "invalid" }, { status: 422 }); } const order = await createOrder(parsed.data); return NextResponse.json(order, { status: 201 }); }

La validazione sta nel handler, non nel client. Lo schema è lo stesso che useresti in una Server Action: `zod`, `valibot` o un parser scritto a mano non cambiano la sostanza.

## Server Actions: il caso normale

Una Server Action è una funzione asincrona marcata `"use server"`. Next.js genera un endpoint POST e collega il form: il browser invia i dati senza che tu scriva un `fetch`.

// app/ordini/actions.ts "use server"; import { z } from "zod"; import { revalidatePath } from "next/cache"; import { createOrder } from "@/lib/orders"; const schema = z.object({ sku: z.string().min(1), qty: z.coerce.number().int().min(1), }); export async function createOrderAction(_prev: unknown, formData: FormData) { const parsed = schema.safeParse(Object.fromEntries(formData)); if (!parsed.success) return { error: "Compila tutti i campi" }; await createOrder(parsed.data); revalidatePath("/ordini"); return { error: null }; }

## Quando il Route Handler è obbligatorio

## Quello che una Server Action non fa

Una Server Action non è un'API. L'URL è generato a build time, non è documentabile e cambia quando cambi la struttura dei file. Non la chiami da `curl` e non la versioni con un contratto.

curl -X POST http://localhost:3000/api/ordini \ -H "content-type: application/json" \ -d '{"sku":"TS-01","qty":2}'

## Il numero che conta

Il catalogo EneUI indicizza 111.985 componenti da 1.472 librerie e 1.556 blocchi, misurati il 1 novembre 2026 su `/api/stats`. Tre categorie coprono i pezzi di questo articolo: form validation, blocchi form e error handling.

## Limite dichiarato

Una Server Action serializza solo valori serializzabili: `Date`, `Map` e istanze di classe non passano. Se il payload è grande o binario, il Route Handler resta la strada più semplice.

Se il chiamante è un form del tuo sito, Server Action. Se il chiamante è qualsiasi altra cosa, Route Handler.

Per il contesto delle cartelle vedi la struttura di un progetto App Router. Per il confine tra moduli server e client c'è server-only e client-only. Per un confronto tra framework leggi Remix vs Next.js.

## FAQ

### Posso chiamare una Server Action da un client mobile?

No. La Server Action è raggiungibile solo dal bundle del sito e il suo URL cambia a ogni build. Per un client esterno scrivi un Route Handler con un contratto stabile.

### Un Route Handler è più lento di una Server Action?

No, girano sullo stesso runtime. Il costo in più è il codice: parsing, validazione e risposta li scrivi tu invece di lasciarli al framework.

### Dove va la validazione?

Nel server, in entrambi i casi. La validazione nel client serve solo all'esperienza d'uso e non sostituisce quella del server.

### Una Server Action può restituire un oggetto?

Sì, se l'oggetto è serializzabile. Restituisci dati semplici e usa il valore di ritorno per lo stato di errore, non per payload grandi.

### Route Handler e Server Action condividono i cookie?

Sì. Entrambi leggono `cookies()` e `headers()` della richiesta in corso, con la stessa API e gli stessi permessi.
