Next.js
React
Server Actions
Fullstack
Web Development

Next.js 15 e azioni del server: la guida definitiva alle mutazioni moderne

Next.js 15 e azioni del server: la guida definitiva alle mutazioni moderne

Lo sviluppo web è in costante flusso. C'è stato un tempo in cui tutto veniva renderizzato sul server (PHP, Ruby on Rails). Quindi spostiamo tutto sul client (SPA con React, Vue). Ora, con l'avvento di React Server Components (RSC) e Next.js App Router, stiamo trovando una potente via di mezzo: il meglio di entrambi i mondi.

E il fiore all'occhiello di questa nuova era nell'ecosistema React sono le Azioni server.

Con il rilascio di Next.js 15, le azioni server non sono più sperimentali e sono diventate lo standard consigliato per la gestione delle mutazioni dei dati. In questa massiccia guida esploreremo ogni dettaglio di questa funzionalità, dalle nozioni di base agli standard avanzati di sicurezza e UX. Se stai ancora scrivendo pages/api/submit.ts per elaborare i moduli, preparati a ritirare gran parte del codice boilerplate.

Cosa sono le azioni del server?

In termini semplici, le azioni server sono funzioni asincrone eseguite sul server, ma possono essere richiamate direttamente dai componenti client o server.

In precedenza, per inviare un modulo, il flusso era:

  1. Creare un componente del modulo sul client ("use client").
  2. Creare uno stato useState per gli ingressi.
  3. Creare una funzione onSubmit che esegue un fetch('/api/submit', ...).
  4. Creare un file API Route (pages/api/submit.ts) per ricevere la richiesta, convalidarla e salvarla nel database.
  5. Gestire manualmente gli errori, gli stati di caricamento e la riconvalida dei dati.

Con Azioni server, il flusso è:

  1. Creare una funzione async che salva in banca.
  2. Passa questa funzione all'elica action di <form>.

FINE. Next.js si occupa della comunicazione, della serializzazione e dell'esecuzione. È una Remote Procedure Call (RPC) realizzata appositamente per il Web.

Configurazione iniziale in Next.js 15

In Next.js 15, le azioni server sono già abilitate per impostazione predefinita. Non è necessario modificare next.config.js.

La convenzione è semplice:

  • Per definire le azioni che possono essere importate nei componenti Client, creare un file con la direttiva "use server" in alto.
  • Per le azioni in linea nei componenti Server, aggiungere "use server" all'interno della funzione.

Esempio base: il "Hello World" delle azioni

Creiamo un'azione per salvare un post del blog. Creare un file src/app/actions.ts:

// src/app/actions.ts 'use server' import { db } from '@/lib/db' import { revalidatePath } from 'next/cache' export async function createPost(formData: FormData) { const title = formData.get('title') as string const content = formData.get('content') as string // Validação básica (em produção use Zod!) if (!title || !content) { throw new Error('Campos obrigatórios faltando') } // Interação direta com o banco (sem API Routes!) await db.post.create({ data: { title, content } }) // A mágica: atualiza a UI instantaneamente revalidatePath('/blog') }

E nel tuo componente:

// src/components/create-post-form.tsx import { createPost } from '@/app/actions' export function CreatePostForm() { return ( <form action={createPost} className="p-4 border rounded"> <input name="title" placeholder="Título" className="border p-2 mb-2 w-full" /> <textarea name="content" placeholder="Conteúdo" className="border p-2 w-full" /> <button type="submit" className="bg-blue-500 text-white p-2 rounded"> Salvar Post </button> </form> ) }

Notare l'assenza di JavaScript sul client per l'invio. Questo modulo funziona anche se l'utente disabilita JS nel browser (Progressive Enhancement), sebbene nelle applicazioni moderne raramente dipendiamo da questo.

Gestione dello stato di carica (useFormStatus)

Una cattiva UX fa clic su "Salva" e non riceve feedback. Poiché stiamo utilizzando la prop HTML nativa action, non abbiamo un manuale useState(loading). React ci fornisce l'hook useFormStatus per questo.

Nota: useFormStatus deve essere utilizzato in un componente reso entro <form>.

// src/components/submit-button.tsx 'use client' import { useFormStatus } from 'react-dom' export function SubmitButton() { const { pending } = useFormStatus() return ( <button type="submit" disabled={pending} className="bg-blue-500 disabled:bg-gray-400 text-white p-2 rounded flex items-center gap-2" > {pending ? 'Salvando...' : 'Salvar Post'} {pending && <Spinner />} </button> ) }

Ora usa semplicemente <SubmitButton /> all'interno del modulo dell'esempio precedente.

Gestione degli errori e feedback (useActionState)

Cosa succede se database fallisce? O convalida? Lanciare semplicemente un errore (throw new Error) non è la migliore UX. Vogliamo restituire i messaggi di errore al modulo.

Per fare ciò, utilizziamo l'hook useActionState (precedentemente useFormState nel React sperimentale). Consente all'azione del server di restituire un valore aggiornato sul client.

Refactoring della nostra azione:

// src/app/actions.ts 'use server' export type FormState = { message: string; errors?: { title?: string[]; content?: string[]; }; } export async function createPostSafely(prevState: FormState, formData: FormData): Promise<FormState> { // Simulação de validação com Zod const validatedFields = schema.safeParse({ title: formData.get('title'), content: formData.get('content'), }); if (!validatedFields.success) { return { message: 'Erro de validação', errors: validatedFields.error.flatten().fieldErrors } } try { await db.post.create({ data: validatedFields.data }) } catch (e) { return { message: 'Erro ao salvar no banco de dados' } } revalidatePath('/blog') return { message: 'Post criado com sucesso!' } }

E nel componente client:

'use client' import { useActionState } from 'react' import { createPostSafely } from '@/app/actions' const initialState = { message: '', errors: {} } export function AdvancedForm() { const [state, formAction] = useActionState(createPostSafely, initialState) return ( <form action={formAction}> {state.message && <p className="text-red-500">{state.message}</p>} <input name="title" /> {state.errors?.title && <p className="text-red-500 text-sm">{state.errors.title[0]}</p>} {/* ... restante do form ... */} </form> ) }

Riconvalida della cache: il potere di revalidatePath

Nel vecchio modello, dopo una mutazione, dovevi recuperare manualmente i dati o utilizzare librerie come React Query o SWR per invalidare le cache.

In Next.js, questo è integrato. La funzione revalidatePath(path) svuota la cache per quel percorso specifico. Alla visita successiva (o immediatamente, se si tratta di una navigazione SPA), Next.js recupera i nuovi dati dal server. C'è anche revalidateTag(tag), che ti dà un controllo granulare se utilizzi il recupero con tag (fetch(url, { next: { tags: ['posts'] } })).

Aggiornamenti ottimistici (useOptimistic)

Per le applicazioni che appaiono "istantanee", non vogliamo attendere che il server risponda per aggiornare l'interfaccia utente. Se aggiungo un elemento all'elenco, voglio vederlo lì adesso.

Il gancio useOptimistic lo consente in modo elegante.

'use client' import { useOptimistic } from 'react' export function PostList({ posts }: { posts: Post[] }) { const [optimisticPosts, addOptimisticPost] = useOptimistic( posts, (state, newPost: Post) => [newPost, ...state] ); async function action(formData: FormData) { const title = formData.get('title') as string; // Atualiza a UI imediatamente addOptimisticPost({ id: Math.random(), title, content: '...' }); // Chama a Server Action real await createPost(formData); } return ( <div> <form action={action}>...</form> <ul> {optimisticPosts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> </div> ) }

Se l'azione del server fallisce, React ripristina automaticamente lo stato ottimistico allo stato reale precedente. È robusto e magico.

Sicurezza: l'elefante nella stanza

Molti sviluppatori guardano "use server" e pensano: "Aspetta, sto esponendo il mio database al client?"

No. Il codice dell'azione del server non viene mai inviato al browser. Viene esposta solo la "firma" della funzione (un URL di riferimento Next.js interno). Tuttavia, è fondamentale trattare le azioni del server come punti di accesso pubblici alla tua API.

Lista di controllo obbligatoria per la sicurezza:

  1. Autenticazione: controlla sempre chi sta chiamando l'azione. Non dare per scontato che l'utente abbia effettuato l'accesso solo perché il pulsante "Salva" era visibile.

    import { auth } from '@/auth' // Auth.js ou Clerk export async function deletePost(id: string) { const session = await auth() if (!session?.user) throw new Error('Não autorizado') // ... }
  2. Autorizzazione: L'utente ha effettuato l'accesso, ma può *eliminare questo post?

    const post = await db.post.findUnique({ where: { id } }) if (post.authorId !== session.user.id) throw new Error('Proibido')
  3. Convalida dell'input: non fidarsi mai di FormData o degli argomenti passati. Utilizza Zod per assicurarti che i dati siano nel formato corretto.

Azioni del server e percorsi API

Dopo tutto, le rotte API sono morte?

Non esattamente, ma il suo utilizzo è diminuito drasticamente.

Utilizza le Azioni del server quando:

  • Hai a che fare con mutazioni di forma.
  • Si desidera richiamare una funzione server da un evento sul client (onClick).
  • Desideri TypeScript digitazione end-to-end senza generare SDK.

Utilizza Route API (gestori di rotte) quando:

  • È necessario esporre una [REST API47 pubblica a terze parti (webhook, app mobili).
  • Sono necessarie funzionalità specifiche del protocollo HTTP nascoste dalle astrazioni React (ad esempio streaming di file binari personalizzati, intestazioni complesse).

Conclusione

Next.js 15 consolida Server Actions come uno dei cambiamenti più produttivi nel recente sviluppo web. Eliminano lo strato intermedio del "codice collante" (percorsi API, fetcher, gestori di stato) e ti consentono di concentrarti su ciò che conta: logica aziendale e interfaccia utente.

Combinando le azioni del server con useActionState, useOptimistic e la convalida con Zod, crei applicazioni robuste e sicure con un'esperienza utente di livello mondiale, scrivendo una frazione del codice che avresti scritto 3 anni fa.

Il futuro è lato server, ma l’esperienza è lato client. E le azioni del server sono il ponte tra loro.


Hai già migrato i tuoi moduli su Azioni server? Qual è stata la sfida più grande? Lascia il tuo commento!

Leggi anche