Server Actions di Next.js 16 nel 2026: mutazioni, revalidation e domande da colloquio

Come le Server Actions di Next.js 16 gestiscono mutazioni, revalidation, stato di attesa, UI ottimistica e sicurezza, con le domande da colloquio che verificano ogni concetto.

Diagramma del flusso di mutazioni e revalidation delle Server Actions di Next.js

Le Server Actions di Next.js trasformano una semplice funzione async in un endpoint di mutazione lato server che un form può chiamare direttamente, senza route API, senza fetch lato client e senza serializzazione JSON manuale. In Next.js 16 sono il modo predefinito per scrivere le mutazioni dei dati e compaiono in quasi ogni colloquio React senior del 2026. Questa analisi approfondita spiega come vengono eseguite, come la revalidation propaga dati aggiornati all'interfaccia, come modellare gli stati di attesa ed errore e le regole di sicurezza che mettono in difficoltà la maggior parte degli sviluppatori.

Definizione in una frase

Una Server Action è una funzione async contrassegnata con "use server" che viene eseguita solo sul server e può essere chiamata da un componente client o da un form come se fosse locale, mentre Next.js gestisce automaticamente la richiesta di rete, la serializzazione degli argomenti e la protezione CSRF.

Come vengono eseguite le Server Actions dietro le quinte

Una Server Action è una funzione eseguita solo sul server e invocata attraverso la rete tramite un endpoint generato dal compilatore. Quando una funzione riporta la direttiva "use server", il compilatore di Next.js non invia mai il suo corpo al browser. Al suo posto sostituisce la funzione con un riferimento leggero: un ID di azione con hash mappato su una route POST generata. Chiamare l'azione dal client invia una richiesta a quella route, la funzione reale viene eseguita sul server e il risultato serializzato viene trasmesso in streaming al chiamante.

Questo design ha tre conseguenze da ricordare sia per il codice di produzione sia per i colloqui. Primo, gli argomenti e i valori di ritorno devono essere serializzabili, perché attraversano la rete come payload codificati. Secondo, i segreti restano sul server, dato che il corpo della funzione non viene mai incluso nel bundle destinato al client. Terzo, le azioni collegate a un <form> funzionano anche prima dell'hydration, motivo per cui il progressive enhancement è un punto di forza centrale. La guida ufficiale di Next.js sull'aggiornamento dei dati documenta l'intero modello di esecuzione.

Ogni Server Action risiede in un file o in un blocco contrassegnato con "use server". Una singola mutazione ha questo aspetto:

app/posts/actions.tstypescript
"use server"

import { revalidatePath } from "next/cache"
import { redirect } from "next/navigation"
import { PostService } from "@/lib/services/post-service"

export async function createPost(formData: FormData) {
  // FormData values arrive as strings; cast explicitly
  const title = formData.get("title") as string
  const body = formData.get("body") as string

  // Run business logic through the service layer, never the ORM directly
  const post = await PostService.create({ title, body })

  // Purge the cached list so the next request refetches it
  revalidatePath("/posts")

  // Send the user to the freshly created resource
  redirect(`/posts/${post.id}`)
}

La funzione legge i FormData inviati, li rende persistenti tramite un service, invalida l'elenco memorizzato in cache e reindirizza. Nessun JavaScript lato client è necessario per tutto questo.

Collegare una Server Action a un form senza JavaScript lato client

Passare l'azione alla prop action di un form è l'integrazione più semplice. Next.js serializza l'invio in FormData e chiama la funzione sul server. Poiché sfrutta il meccanismo nativo di invio dei form, funziona anche con JavaScript disabilitato e si aggiorna senza interruzioni una volta che la pagina completa l'hydration.

app/posts/new-post-form.tsxtsx
import { createPost } from "./actions"

export default function NewPostForm() {
  return (
    <form action={createPost}>
      <input name="title" required />
      <textarea name="body" required />
      <button type="submit">Publish</button>
    </form>
  )
}

L'attributo name di ogni campo diventa la chiave letta da formData.get(). Il riferimento MDN su FormData spiega come il browser assembla questo oggetto a partire dal form. Per un controllo più ricco, next/form estende l'elemento nativo con navigazione lato client e prefetching, mantenendo lo stesso contratto basato sulle azioni.

Gestire lo stato di attesa ed errore con useActionState

I form reali hanno bisogno di feedback di validazione e di un indicatore di caricamento. L'hook di React 19 useActionState avvolge una Server Action, propaga un valore di stato attraverso ogni invio ed espone un flag isPending. Sostituisce la vecchia denominazione useFormState e restituisce una tupla composta dallo stato corrente, dall'azione avvolta e dal booleano di attesa.

app/posts/new-post-form.tsxtsx
"use client"

import { useActionState } from "react"
import { createPost } from "./actions"

const initialState = { error: "" }

export default function NewPostForm() {
  // state carries the last return value; isPending tracks the in-flight request
  const [state, formAction, isPending] = useActionState(createPost, initialState)

  return (
    <form action={formAction}>
      <input name="title" required />
      <textarea name="body" required />
      {state.error ? <p role="alert">{state.error}</p> : null}
      <button type="submit" disabled={isPending}>
        {isPending ? "Publishing..." : "Publish"}
      </button>
    </form>
  )
}

Quando un hook guida l'azione, la firma della funzione acquisisce un primo parametro prevState prima di FormData. Restituire un oggetto invece di lanciare un'eccezione permette al componente di mostrare la validazione inline senza un error boundary:

app/posts/actions.tstypescript
"use server"

import { revalidateTag } from "next/cache"
import { PostService } from "@/lib/services/post-service"

type FormState = { error: string }

export async function createPost(
  prevState: FormState,
  formData: FormData,
): Promise<FormState> {
  const title = formData.get("title") as string

  // Validate on the server; client validation is never trustworthy alone
  if (!title || title.length < 3) {
    return { error: "Title must be at least 3 characters." }
  }

  await PostService.create({ title })

  // Invalidate by tag rather than path for finer-grained control
  revalidateTag("posts")
  return { error: "" }
}

Il riferimento a useActionState su react.dev illustra pattern avanzati come il supporto ai permalink per gli invii precedenti all'hydration. Restituire uno stato strutturato mantiene il percorso corretto e quello di errore in un unico punto prevedibile.

revalidatePath vs revalidateTag: scegliere l'invalidazione giusta

Una mutazione è solo metà del lavoro: l'interfaccia deve rifletterla. Next.js espone due funzioni di invalidazione da next/cache, e scegliere quella corretta è un discriminante frequente nei colloqui.

| Funzione | Invalida | Ideale quando | |----------|-------------|-----------| | revalidatePath("/posts") | Ogni voce in cache di una route | La mutazione riguarda una pagina o un layout ben preciso | | revalidateTag("posts") | Ogni fetch in cache etichettata con quel tag | Gli stessi dati compaiono su più route |

revalidatePath è grossolana e orientata alle route: svuota la cache per un URL e per il suo albero di layout. revalidateTag è granulare e orientata ai dati: qualsiasi fetch o funzione in cache etichettata con quella stringa viene svuotata ovunque si trovi. I tag scalano meglio nelle applicazioni di grandi dimensioni, perché una singola mutazione può aggiornare una sidebar, un elenco e una pagina di dettaglio con una sola chiamata. Entrambe le funzioni contrassegnano i dati come obsoleti anziché ricaricarli immediatamente, così la richiesta successiva rigenera il contenuto.

Con i Cache Components di Next.js 16, il tagging si integra con la direttiva "use cache" tramite cacheTag, cosa che cambia il modo di ragionare sulla cache in tutta l'applicazione. Questa interazione è trattata in modo approfondito nella guida ai Cache Components di Next.js 16 su SharpSkill.

Pronto a superare i tuoi colloqui su React / Next.js?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

Aggiornamenti ottimistici dell'interfaccia con useOptimistic

I round trip di rete aggiungono una latenza percepibile. L'hook useOptimistic mostra istantaneamente il risultato atteso, poi si riconcilia con la risposta del server una volta che l'azione si risolve. Riceve lo stato corrente e un reducer che produce la versione ottimistica.

app/posts/like-button.tsxtsx
"use client"

import { useOptimistic } from "react"
import { likePost } from "./actions"

export function LikeButton({ postId, likes }: { postId: string; likes: number }) {
  // optimisticLikes updates before the server confirms
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    likes,
    (current) => current + 1,
  )

  async function handleLike() {
    addOptimisticLike(null) // update the UI immediately
    await likePost(postId)  // then run the real mutation
  }

  return (
    <form action={handleLike}>
      <button type="submit">Like ({optimisticLikes})</button>
    </form>
  )
}

Se l'azione fallisce, React ripristina automaticamente il valore ottimistico allo stato confermato dal server, quindi non è necessario alcun rollback manuale. Il riferimento a useOptimistic su react.dev descrive nel dettaglio come il ciclo di riconciliazione interagisce con le transizioni. È questo pattern a far percepire i form basati su Server Actions reattivi quanto una SPA renderizzata interamente sul client.

Associare argomenti e chiamare le azioni dagli event handler

Non tutte le mutazioni hanno origine dall'invio di un form. Eliminare una riga, attivare o disattivare un'impostazione o riordinare un elenco spesso parte dal clic su un pulsante o da un altro evento. Sul client una Server Action è un normale riferimento a funzione, quindi può essere chiamata da qualsiasi handler, a patto che la chiamata avvenga all'interno di una transizione per mantenere l'interfaccia reattiva.

Gli argomenti aggiuntivi che non sono campi del form si collegano con bind, che produce una nuova azione con quei valori anteposti. È il modo idiomatico per passare un ID insieme ai FormData inviati.

app/posts/post-actions.tsxtsx
"use client"

import { useTransition } from "react"
import { deletePost, updatePost } from "./actions"

export function PostActions({ postId }: { postId: string }) {
  const [isPending, startTransition] = useTransition()

  // Bind the id so the action receives it before FormData
  const updateWithId = updatePost.bind(null, postId)

  return (
    <div>
      <form action={updateWithId}>
        <input name="title" />
        <button type="submit">Save</button>
      </form>

      {/* Event-handler invocation wrapped in a transition */}
      <button
        onClick={() => startTransition(() => deletePost(postId))}
        disabled={isPending}
      >
        {isPending ? "Deleting..." : "Delete"}
      </button>
    </div>
  )
}

Gli argomenti associati sono cifrati come qualsiasi altro valore catturato in closure, quindi il postId non viene manomesso durante il transito, anche se l'azione deve comunque verificare che il chiamante sia autorizzato ad agire su quel record. Avvolgere la chiamata diretta in startTransition permette a React di mantenere l'interfaccia interattiva ed espone lo stato di attesa senza bisogno di un form.

Sicurezza delle Server Actions: ogni azione è un'API pubblica

L'equivoco più pericoloso è credere che una Server Action sia privata perché scritta accanto al codice server. In realtà, non appena un'azione viene utilizzata, Next.js genera un endpoint POST pubblico che chiunque può chiamare con una richiesta costruita ad hoc. Il framework fornisce ID di azione sicuri ed elimina le azioni inutilizzate durante next build, ma non autorizza il chiamante.

Autorizzare all'interno di ogni azione

Le Server Actions sono endpoint HTTP raggiungibili. I controlli di sessione in un layout o in un middleware non le proteggono, perché un aggressore può inviare una richiesta POST direttamente all'azione. Occorre verificare la sessione e i permessi del chiamante all'interno del corpo dell'azione prima di toccare qualsiasi dato.

app/admin/actions.tstypescript
"use server"

import { auth } from "@/lib/auth"
import { headers } from "next/headers"
import { UserService } from "@/lib/services/user-service"

export async function deleteUser(userId: string) {
  // Authenticate on every invocation, not in a wrapper component
  const session = await auth.api.getSession({ headers: await headers() })
  if (!session || session.user.role !== "admin") {
    throw new Error("Unauthorized")
  }

  await UserService.delete(userId)
}

Contano altre due regole. I valori catturati in closure da un'azione inline vengono cifrati prima di essere inviati al client e decifrati sul server, quindi un token catturato non viene esposto in chiaro, ma affidarsi a questo è fragile: è meglio tenere del tutto i segreti fuori dalle closure. Inoltre, qualsiasi argomento proveniente dal client è input non attendibile che deve essere validato, esattamente come il body di una richiesta REST. La guida alla sicurezza dei dati di Next.js formalizza il modello di taint alla base di queste garanzie. Per esercitarsi in modo strutturato su questi compromessi, il modulo di domande da colloquio sulle Server Actions di Next.js su SharpSkill propone esattamente le domande poste dalle commissioni di selezione.

Domande da colloquio sulle Server Actions di Next.js 16

Queste domande ricorrono con costanza nei colloqui React e full-stack senior del 2026.

Perché una Server Action può accettare e restituire solo valori serializzabili? Perché argomenti e risultati attraversano il confine di rete tra client e server come payload codificati. Funzioni, istanze di classi e nodi del DOM non possono essere serializzati, quindi passarli genera un'eccezione. FormData, oggetti semplici, array e valori primitivi sono sicuri.

Come funziona il progressive enhancement con le Server Actions? Quando un'azione viene passata direttamente alla prop action di un form, il browser invia il form in modo nativo tramite una richiesta POST ancora prima che React esegua l'hydration. Next.js intercetta ed esegue l'azione lato server, così il form è funzionante senza JavaScript lato client e si aggiorna a un'esperienza guidata dal client dopo l'hydration.

Qual è la differenza tra lanciare e restituire un errore in un'azione? Lanciare un'eccezione la propaga all'error boundary più vicino ed è la scelta giusta per i guasti imprevisti. Restituire un oggetto di stato strutturato tramite useActionState è la scelta giusta per gli errori di validazione previsti, che dovrebbero essere mostrati inline senza smontare il form.

Chiamare revalidatePath ricarica i dati immediatamente? No. Contrassegna le voci in cache come obsolete, così la richiesta successiva le rigenera. La risposta corrente non ne è influenzata, ed è per questo che un redirect dopo una mutazione porta a contenuti appena renderizzati.

Per il contesto di rendering più ampio in cui vengono eseguite queste mutazioni, l'analisi React 19 Server Components in produzione si abbina naturalmente alla conoscenza delle Server Actions.

Pronto a superare i tuoi colloqui su React / Next.js?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

Conclusione

Le Server Actions di Next.js 16 consolidano mutazioni, revalidation e progressive enhancement in un unico primitivo server-first. I punti chiave:

  • Contrassegnare le mutazioni con "use server" e passarle alla prop action di un form per ottenere gratuitamente la gestione della rete e il progressive enhancement.
  • Gestire lo stato di attesa e di validazione con useActionState, restituendo un oggetto strutturato per gli errori previsti invece di lanciare eccezioni.
  • Ricorrere a revalidatePath quando cambia una sola route e a revalidateTag quando gli stessi dati si estendono su più route.
  • Usare useOptimistic per mostrare istantaneamente il risultato atteso e lasciare che React riconcili o esegua il rollback automaticamente.
  • Autorizzare e validare all'interno del corpo di ogni azione, perché ogni azione utilizzata è un endpoint HTTP pubblico che il solo middleware non protegge.

Inizia a praticare!

Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.

Tag

#next.js
#server-actions
#react
#mutations
#revalidation
#interview

Condividi

Articoli correlati