Web Design and Engineering

Server Components vs Client Components: l'albero decisionale che seguiamo

L'albero decisionale in cinque passi che usiamo su ogni progetto Next.js 16 per capire quando un componente resta server e quando serve "use client".

20 aprile 20267 min di lettura
Server Components vs Client Components: l'albero decisionale che seguiamo

Alla fine di questa guida avrai una regola chiara e ripetibile per decidere quando scegliere un Server Component e quando marcare un file con "use client". È lo stesso albero decisionale che applichiamo su ogni progetto Next.js 16 che spediamo in produzione, e toglie di mezzo la maggior parte dei dibattiti "dove va questa logica?" in fase di pull request.

La domanda è pratica, non teologica. Next.js 16 e React 19 rendono i Server Components il default, quindi ogni componente dentro app/ parte sul server a meno che tu non decida diversamente. L'errore che fanno molti team è l'inverso: marcano tutta la pagina "use client" appena un figlio ha bisogno di stato, e poi spediscono un bundle JavaScript che ricostruisce l'intero albero nel browser senza motivo.

Prima di iniziare

  • Un progetto Next.js 16 che usa l'App Router (directory app/).
  • React 19 (installato di default in Next.js 16).
  • Dimestichezza con le due direttive: "use client" e "use server". Questa guida copre la prima.

L'albero decisionale in cinque passi

Applica i passi in ordine. Fermati al primo la cui risposta è sì.

Passo 1. Il componente ha bisogno di stato, eventi, effetti o API del browser?

È l'unica domanda che la documentazione ufficiale di React tratta come regola rigida. I Client Components sono obbligatori quando usi useState, useEffect, gestori eventi tipo onClick, localStorage, window, navigator, o qualsiasi hook di terze parti che incapsuli uno di questi. Se la risposta è sì, metti "use client" all'inizio del file, sopra gli import, e prosegui (documentazione React sulla direttiva).

Se la risposta è no, default a Server Component. Nessuna direttiva necessaria. I Server Components sono il default in Next.js 16 (Next.js server e client components).

Passo 2. Puoi spingere il confine "use client" più in basso nell'albero?

È il passo che i team saltano più spesso. Quando un file è marcato "use client", ogni file che importa diventa parte del bundle client. Inclusa qualunque logica server-only che ti sei tirato dietro per sbaglio. La soluzione è isolare il pezzo interattivo in un file a sé e tenere il genitore lato server.

Esempio concreto: una pagina di dettaglio prodotto con un bottone "Acquista". La pagina legge il prodotto dal database (lavoro server), rende la maggior parte del layout (statico), e ha bisogno di un solo pezzo interattivo per il carrello. Il bottone è il Client Component. La pagina, l'immagine, la descrizione e la tabella delle specifiche restano server. Così il payload JavaScript si limita al bottone e alle sue dipendenze.

Passo 3. L'interattività ha bisogno di dati da un Server Component?

Non puoi importare un Server Component dentro un Client Component. Il runtime React è a senso unico: il server rende per primo, il client fa hydration dopo. Importare attraverso il confine costringerebbe il client a richiamare il server, che è esattamente ciò che i React Server Components esistono per evitare.

Il pattern supportato è la composizione tramite children o altre prop di tipo React-node. Un Server Component genitore importa sia il Client Component sia il Server Component, poi passa il Server Component come figlio. Il Client Component lo riceve come JSX opaco e decide dove posizionarlo (pattern di composizione Next.js). Noi usiamo questa forma per modali, drawer e tab che devono ospitare contenuti renderizzati sul server.

Passo 4. Il componente è usato solo dentro un sottoalbero client?

Se la risposta è sì, è già di fatto un Client Component. Una singola direttiva "use client" al confine ribalta tutto il sottoalbero. Aggiungere la direttiva a ogni figlio è ridondante e rumoroso in code review. Mettila solo nel punto d'ingresso dove un Server Component importa nel mondo client.

Passo 5. Misura prima di migrare.

I risparmi sul bundle per lo spostamento di lavoro sul server variano di ordini di grandezza. Frigade ha documentato una riduzione del 62% dopo una migrazione RSC perché un renderer markdown corposo è passato sul server (Frigade engineering, 2024). Altre app vedono guadagni a una cifra percentuale. La differenza sta nel fatto che le librerie che ospiti lato client possano davvero girare sul server. Syntax highlighter, parser markdown, formattatori di date e helper i18n di solito sono portabili. Rich text editor, librerie di grafici e di mappe di solito no.

Prima di promettere a un team un guadagno di performance, esegui @next/bundle-analyzer o ispeziona l'output di next build, segnala i primi cinque import lato client e verifica se qualcuno può passare server-side. Se nessuno può, l'albero decisionale si ferma alla forma attuale e la resa è minima.

Verifica che funzioni

  • Pannello Network. I Server Components emettono HTML senza un chunk JavaScript corrispondente per i loro contenuti. Apri DevTools, ricarica a cache fredda e conferma che l'HTML iniziale contiene il tuo testo. Se il testo appare solo dopo il caricamento di JavaScript, il componente è client.
  • Bundle analyzer. Esegui ANALYZE=true next build con @next/bundle-analyzer configurato. Le foglie Client Component compaiono come chunk nominati. Se vedi un chunk per qualcosa che dovrebbe essere statico, il confine "use client" è più in alto del dovuto.
  • Visualizza sorgente. Cerca nel sorgente HTML il tuo testo statico. Presente nell'HTML: renderizzato server. Solo dentro uno <script>: renderizzato client.

Errori comuni e fix

Errore: "Functions cannot be passed directly to Client Components". Stai passando una prop funzione da un Server Component a un Client Component. Inline la logica sul client, oppure usa una Server Action (direttiva "use server" sulla funzione) in modo che il runtime la trasporti attraverso il confine.

Hydration mismatch su un componente che dovrebbe essere statico. Una libreria di terze parti che importi usa internamente hook React, contaminando il file come eseguito lato client. Sposta l'import dietro un wrapper Client Component, oppure sostituisci la libreria.

Intera pagina marcata "use client". Il sintomo è un singolo chunk JavaScript grosso per la rotta. Suddividi la pagina: tieni page.tsx lato server, sposta il blocco interattivo in un file fratello, importa il fratello dalla pagina server.

Provider di contesto alla root. Theme provider, query client e auth provider sono Client Components che avvolgono tutta l'app. Va bene così. Non promuovono i figli al client se i figli stessi non sono client. Next.js emette comunque HTML server per figli Server Component avvolti in un provider client.

Fetch dati in un Client Component con useEffect. È un pattern Pages Router. In App Router, fai il fetch nel Server Component, passa i dati come prop e lascia che il Client Component li renderizzi. Se i dati sono lenti, avvolgi il componente che fetch in <Suspense> e stream (guida streaming Next.js).

Per approfondire

Due superfici estendono naturalmente questo albero. Per le mutazioni di dati, usa le Server Actions (direttiva "use server" su una funzione async). Per il rendering progressivo di dati server lenti senza bloccare la pagina, combina Suspense con loading.tsx.

Il modello mentale dei Server Components di Josh W. Comeau è il miglior primer esterno che abbiamo letto (Making Sense of React Server Components). Inquadrare i Server Components come output di build, non come paradigma a runtime, aiuta i team che vengono dal mondo SSR-più-hydration di Next.js 13 e precedenti. Se sei ancora dentro quel modello, il primo ritorno è concettuale, non di performance.

La regola a cui torniamo su ogni progetto: il default è server. L'eccezione è documentata, locale, e piccola quanto può esserlo. Quella regola ha fermato più regressioni sul bundle di qualunque lint che abbiamo provato.

Foto di Kelly Sikkema su Unsplash

Studio

Inizia un progetto.

Un partner unico per aziende, PA, startup e SaaS. Produzione più veloce, tecnologie moderne, costi ridotti. Un team, una fattura.