Server Actions di Next.js in produzione: cosa usiamo e cosa evitiamo
Ogni Server Action che esporti è un endpoint POST pubblico senza autenticazione. Ecco i pattern Next.js 16 che mandiamo in produzione e quelli che rifiutiamo.
Una Server Action di Next.js è una funzione lato server che chiami come se fosse locale, e questo nasconde il dettaglio che decide se la tua app è sicura: ogni chiamata è una richiesta POST pubblica sulla rete.
Si scelgono le Server Actions perché tolgono di mezzo il layer API. Un form chiama una funzione, la funzione scrive sul database, la pagina si aggiorna. Niente route handler, niente fetch dal client, nessun contratto di richiesta da tenere allineato. Il vantaggio è concreto, e noi usiamo le Server Actions su gran parte dei progetti SaaS. È anche il punto dove nascono gli errori. La funzione sembra codice privato, quindi viene trattata come codice privato. Non lo è. Ecco i pattern che teniamo in produzione su Next.js 16 e quelli che rifiutiamo, dopo che il framework si è assestato con React 19 e il modello di cache di Next.js 16.
Perché una Server Action è più rischiosa di quanto sembri
Quando marchi una funzione con la direttiva 'use server' e la esporti, Next.js la compila in un endpoint. Ogni action diventa un endpoint POST pubblico senza autenticazione, raggiungibile con una richiesta diretta e non solo tramite il tuo form. Next.js aggiunge una difesa a livello di framework: confronta l'Origin della richiesta con l'Host (o X-Forwarded-Host) e rifiuta se non combaciano, il che blocca i POST cross-site più banali. Se lavori dietro un proxy o una CDN su un altro dominio, li elenchi in serverActions.allowedOrigins. La protezione del framework finisce qui. Non sa chi è il tuo utente, cosa possiede, né se quella riga è sua da toccare. Come dice la guida alla sicurezza di Next.js, le protezioni del framework non sostituiscono i controlli a livello applicativo.
Perché "è solo una funzione" è il modello mentale sbagliato
La correzione più comune è proteggere il confine una volta sola, nel middleware o in un layout, dando per scontato che tutto ciò che sta sotto sia al sicuro. Non lo è. Il middleware gira sulla navigazione, non a ogni invocazione di action, e un controllo nel layout protegge ciò che viene reso, non ciò che un attaccante manda in POST direttamente. Tratta ogni action come una porta d'ingresso a sé. Il principio è uno: il perimetro è per singola action, non per rotta.
I pattern che usiamo
Valida l'input con Zod, poi controlla l'autorizzazione a parte
Validazione e autorizzazione sono due lavori diversi, e confonderli è il bug più frequente che vediamo sulle Server Actions. Un oggetto ben formato può comunque puntare a una riga che chi chiama non possiede. Quindi facciamo entrambe le cose, in ordine: analizziamo il payload con uno schema Zod, poi verifichiamo che chi chiama possa agire su quella specifica risorsa.
'use server'
export async function deleteProject(input) {
const { id } = DeleteSchema.parse(input) // validazione
const user = await requireUser() // autenticazione
const project = await projects.findById(id)
if (project.ownerId !== user.id) { // autorizzazione
return { ok: false, error: 'not_allowed' }
}
await projects.remove(id)
return { ok: true }
}Metti ogni lettura e scrittura dietro un Data Access Layer
L'accesso al database sta in un Data Access Layer solo-server che restituisce DTO minimi, mai righe grezze. L'action chiama il layer, il layer impone la proprietà e dà forma all'output. Così un campo di troppo, un hash di password o un flag interno, non finisce dentro un componente client. E ti dà un punto unico dove aggiungere il rate limiting sulle operazioni costose.
Restituisci un risultato tipizzato, non lanciare eccezioni oltre il confine
Da ogni action restituiamo un'unione di risultati prevedibile, tipo { ok: true, data } oppure { ok: false, error }, invece di lanciare eccezioni. Un'eccezione attraversa la rete come un fallimento opaco che la UI non sa mostrare bene. Un risultato tipizzato lascia che il form mostri il messaggio giusto e tiene onesti client e server grazie a TypeScript. È la stessa disciplina dell'RPC type-safe con le Server Actions.
Lascia che React 19 gestisca lo stato pending e la UX ottimistica
Le Server Actions si abbinano a tre hook di React 19. useActionState tiene il risultato restituito e ti dà il progressive enhancement, così un form funziona anche con JavaScript disattivato. useFormStatus espone lo stato pending di un pulsante di invio senza passare props a catena. useOptimistic aggiorna la UI prima che il giro di rete finisca, poi riconcilia quando arriva il risultato reale. La UI ottimistica la usiamo sulle mutazioni frequenti e a basso rischio (toggle, riordini), non su quelle distruttive.
Scegli la giusta chiamata di invalidazione della cache
Next.js 16 divide l'invalidazione della cache in strumenti distinti, e sbagliare è un bug silenzioso in produzione. Usa revalidatePath per aggiornare una rotta dopo una mutazione. Usa updateTag dentro un'action quando l'utente deve leggere la propria scrittura nello stesso giro: scade il tag subito e la richiesta successiva aspetta i dati freschi. Usa revalidateTag, che ora vuole un profilo cacheLife, per contenuti che tollerano lo stale-while-revalidate. Abbina la chiamata al fatto che l'utente debba vedere la modifica adesso oppure più avanti.
Registra ogni fallimento che restituisci
Dato che restituiamo fallimenti tipizzati invece di lanciare eccezioni, un tentativo non autorizzato o un errore di validazione può passare in silenzio se non lo registri. Perciò ogni action logga il ramo di fallimento con l'id di chi chiama e il motivo, prima di restituirlo. Così l'unione di risultati diventa una traccia di audit: tanti risultati not_allowed dallo stesso account sono un segnale da mettere in allerta, non un avviso rosso che vede solo l'utente. Costa una riga per action ed è l'osservabilità più economica che aggiungerai nel trimestre.
I pattern che evitiamo
- Usare una Server Action per leggere dati. Le action sono solo POST e girano in serie, non sono lo strumento per le letture in stile GET. Leggi in un Server Component o in un Route Handler.
- Accettare upload grandi direttamente. Le richieste di una action sono limitate a 1 MB di default. Puoi alzare
serverActions.bodySizeLimit, ma per file oltre qualche megabyte usiamo un Route Handler con streaming, o un upload diretto verso lo storage. - Fidarsi del client per l'id utente. L'id di chi chiama arriva dalla sessione sul server, mai da un campo del form.
- Un'unica action gigante che fa cinque cose. Action piccole e monouso sono più facili da autorizzare, testare e capire.
- Ignorare la rotazione degli id. Next.js ruota gli id delle Server Action almeno ogni 14 giorni, quindi un utente con una scheda vecchia può chiamare un id che non esiste più. Sui deploy self-hosted multi-istanza imposta una
NEXT_SERVER_ACTIONS_ENCRYPTION_KEYstabile e condivisa fra le istanze, e gestisci il caso "Failed to find Server Action" con un invito a ricaricare.
Come appare in pratica
In un progetto recente, un form delle impostazioni chiamava una sola action updateWorkspace. Lo schema Zod rifiutava l'input malformato, il Data Access Layer confermava che chi chiamava fosse admin di quel workspace, l'action restituiva un risultato tipizzato e useActionState mostrava l'errore in linea senza ricaricare la pagina. A scrittura riuscita, updateTag aggiornava il nome del workspace ovunque comparisse nella stessa risposta. L'intero percorso sta in un file, senza rotta API, e regge anche come semplice invio di form se uno script non carica. È il compromesso che ci piace: meno impianto idraulico, a patto che ogni action difenda la propria porta.
Nel 2026 le Server Actions sono pronte per la produzione e sostituiscono gran parte delle route API semplici per le mutazioni. Non sostituiscono il giudizio. Valida, autorizza, restituisci un risultato tipizzato e tieni le letture fuori. Fallo su ogni action, non una volta sola al bordo, e il confine di rete invisibile smette di essere un rischio.
Domande frequenti
Le Server Actions di Next.js sono sicure di default?
In parte. Next.js offre una difesa a livello di framework: confronta l'Origin della richiesta con l'Host e rifiuta i POST cross-site, e accetta solo il metodo POST. Non è autenticazione né autorizzazione. Ogni action che esporti è un endpoint POST pubblico raggiungibile con una richiesta diretta, quindi devi aggiungere tu l'autenticazione e un controllo di proprietà o permessi dentro ogni action. La protezione del framework è un pavimento, non un muro.
Quando conviene usare un Route Handler invece di una Server Action?
Usa un Route Handler per le letture, per gli upload oltre 1 MB e per tutto ciò che chiama un client non-browser, come un webhook o un'API pubblica. Le Server Actions sono solo POST, girano in serie e hanno un limite di 1 MB sul corpo di default. Sono adatte alle mutazioni innescate dalla tua UI. Le letture stanno nei Server Component o nei Route Handler, e gli upload di file grandi stanno in un Route Handler con streaming o in un flusso diretto verso lo storage.
Le Server Actions funzionano senza JavaScript?
Sì, se le colleghi tramite un form e useActionState. Dato che l'action è un vero endpoint POST, il browser può inviare il form e ottenere una risposta resa dal server anche se lo script client non carica mai. È il progressive enhancement, ed è un motivo per preferire il pattern form-più-useActionState al chiamare un'action da un handler onClick. La UI ottimistica con useOptimistic richiede JavaScript, quindi è un miglioramento in più, non la base.
Perché ottengo 'Failed to find Server Action' dopo il deploy?
Next.js identifica ogni Server Action con un id inserito nella build, e ruota quegli id almeno ogni 14 giorni. Un utente fermo su una scheda vecchia di un deploy precedente può invocare un id che la nuova build non conosce più, e scatta quell'errore. Su hosting a singola istanza basta ricaricare. Sui deploy self-hosted multi-istanza imposta una NEXT_SERVER_ACTIONS_ENCRYPTION_KEY stabile e condivisa fra le istanze, così gli id restano coerenti, e intercetta l'errore per invitare l'utente a ricaricare.
Articoli correlati
Studio
Inizia un progetto.
Un partner unico per tutto il progetto. Produzione più veloce, tecnologie moderne, costi ridotti.