Errori e fix

Server Component che importa un hook client: l'errore e le tre uscite

Un Server Component non può usare stato, effetti o API del browser. L'errore è esplicito: la correzione è spostare il confine, non aggirarlo con un cast.

In sintesi

  • `"use client"` va nel file che usa l'hook, non in un antenato qualsiasi: il confine è per modulo.
  • Un Server Component può passare props serializzabili a un Client Component, non funzioni.
  • Sposta l'hook nella foglia più piccola: il confine client resta limitato e il bundle non cresce.

Risposta breve: un Server Component non può usare hook. Aggiungi "use client" al file che contiene l'hook, oppure sposta l'hook nella foglia più piccola possibile. Non aggirare l'errore con un cast.

Il messaggio esatto

You're importing a component that needs `useState`. This React hook
only works in a Client Component but none of its parents are marked
with a "use client" directive, so they're Server Components by default.

Il messaggio può nominare useEffect o useRef: cambia l'hook, non la causa. React segnala il primo che incontra nel modulo.

Come funziona il confine

React compila i moduli uno alla volta e la direttiva marca un modulo, non un componente. Da quel punto in poi il bundler costruisce due grafi: uno per il server e uno per il client. Il grafo server non espone gli hook, perché la build risolve react con la condizione react-server, che contiene solo la parte server. Un file senza direttiva viene compilato in quel grafo, quindi useState non esiste come export e la build si ferma prima di eseguire il codice.

Nell'App Router il default è server, e vale anche per i figli: tutto ciò che un modulo client importa diventa client, mentre le props che ricevono da un genitore server restano renderizzate sul server. La conseguenza pratica è che il confine va tracciato dal basso: un solo "use client" in cima all'albero porta nel bundle ogni import di ogni discendente.

La correzione minima

"use client";
import { useState } from "react";

export function Counter() {
  const [n, setN] = useState(0);
  return  setN(n + 1)}>{n};
}

La direttiva vale per il file, non per il componente. Se il file importa una libreria grande, quella libreria entra nel bundle client: per questo la direttiva va messa il più in basso possibile.

La correzione migliore: confine piccolo

// app/page.tsx — Server Component
import { Counter } from "./counter";

export default async function Page() {
  const total = await getTotal();
  return ;
}

Il Server Component carica i dati e passa un valore serializzabile. Il Client Component gestisce l'interazione. Il confine è di un solo file e il resto della pagina resta sul server.

Il passo diagnostico

# files that call a hook but never declare the boundary
# Windows: run this in Git Bash
grep -rlE "use(State|Effect|Ref|Reducer)\(" app components | xargs grep -L "use client"

L'elenco che esce contiene i candidati, non i colpevoli: un componente può usare un hook e ricevere l'interazione da un genitore client senza problemi. Il colpevole è il primo file della catena d'import che compare nel messaggio d'errore e che sta in quell'elenco. Parti da lì, non dalla pagina: se il file è una foglia di presentazione, sposta l'hook in un wrapper e lascia la foglia senza stato.

Quando la correzione non funziona

"use client" non rende client i figli passati come children: un Server Component passato dentro un Client Component resta server. Se l'hook continua a fallire, il file che lo usa non è quello che hai marcato, oppure il componente arriva da un pacchetto che non dichiara la direttiva. In quel caso serve un wrapper client locale.

Un secondo errore comune è passare una funzione come prop a un Client Component: non è serializzabile. Passa dati, oppure usa una Server Action per la mutazione.

Quando la correzione comune peggiora le cose

La scorciatoia più copiata è mettere la direttiva in cima a page.tsx o layout.tsx. Sembra funzionare finché la pagina è sincrona: appena c'è un await, il componente client non può essere asincrono e l'errore cambia forma invece di sparire. Nello stesso movimento tutto l'albero sotto il file diventa client, quindi ogni libreria importata entra nel bundle, e il costo lo paghi in produzione, dove non c'è più nessun avviso a ricordartelo.

Un limite con un nome

Limite dichiarato: un modulo con "use client" non può esportare metadata né generateMetadata. Se sposti la direttiva nella pagina per far funzionare un hook, il titolo e la description smettono di essere generati sul server, e nessun test di rendering se ne accorge. La via d'uscita è tenere la pagina come server component e spostare solo l'interazione in un figlio.

Come prevenirlo

  • Metti "use client" solo nei file con stato, effetti o eventi.
  • Tieni i componenti di presentazione senza hook: restano server e non pesano nel bundle.
  • Controlla il peso del bundle client dopo ogni nuovo confine, non a fine progetto.
  • Se un pacchetto di terze parti non dichiara la direttiva, avvolgilo in un wrapper locale invece di marcare il chiamante.

Dove continuare

Nel catalogo: componenti, sidebar con stato e blocchi. Le guide: App Router, struttura e confini e peso di una libreria UI; il confronto: copy-paste o headless.

Domande frequenti

Perché l'errore nomina `useState` se uso `useEffect`?

React segnala il primo hook che incontra nel modulo. Il problema è lo stesso: il file non è un Client Component.

`"use client"` rende client anche i figli?

No. I figli passati come `children` da un Server Component restano Server Component: il confine vale per il modulo che lo dichiara.

Posso passare una funzione a un Client Component?

No, le props devono essere serializzabili. Passa dati o usa una Server Action per la mutazione.

Perché il bundle è cresciuto dopo `"use client"`?

Il confine porta nel bundle client tutto ciò che il modulo importa. Se il file importa una libreria grande, quella libreria entra nel client.

Serve `"use client"` in ogni file con un evento?

Sì, se il gestore è definito lì. Se il gestore arriva da un componente client già dichiarato, il file può restare server.