React
Errori di build comuni nell’App Router e la loro causa
Cinque errori di build ricorrenti nell’App Router, la causa reale e la correzione: window is not defined, hook senza use client, hydration.
In sintesi
- Tre cause coprono quasi tutto: API Node nel client, hook senza direttiva, funzione passata a un client component.
- La build si ferma al primo confine rotto: leggi il primo errore, non l’ultimo.
- Nessuno di questi errori si riproduce allo stesso modo in sviluppo.
Gli errori di build nell’App Router hanno quasi sempre tre cause: un modulo Node importato nel browser, un hook dentro un server component, un valore non serializzabile passato a un client component. Il messaggio nomina il file, la causa è nel confine.
I cinque che si vedono di più
| Messaggio | Causa | Correzione |
|---|---|---|
window is not defined | Un modulo browser valutato durante il render server | useEffect oppure dynamic con ssr: false |
Module not found: Can't resolve 'fs' | API Node in un componente client | Sposta la lettura in un server component o in route.ts |
You're importing a component that needs useState | Hook in un file senza "use client" | Aggiungi la direttiva sul componente più basso |
Event handlers cannot be passed to Client Component props | Funzione passata da server a client | Passa un dato o usa una Server Action |
Hydration failed because the server rendered HTML didn't match the client | Markup diverso tra server e client | Rendi deterministico il primo render |
Il primo: window is not defined
Il modulo che tocca window viene valutato quando il server importa il componente. La correzione è spostare l’accesso dentro useEffect, oppure caricare il componente solo sul client. La diagnosi completa è in finestra non definita in Next.js.
Il terzo: la direttiva nel posto sbagliato
"use client" vale per il file e per tutto ciò che importa. In cima al layout radice rende client l’intero albero; sul componente foglia limita il confine. La mappa dei due mondi è in server-only e client-only.
# riproduci la build prima del push
npx tsc --noEmit && npm run build 2>&1 | tail -n 40
Il quinto: hydration
Il markup del server e quello del client devono coincidere al primo render. Date.now(), Math.random(), localStorage e il fuso orario del browser producono differenze. La correzione è un render iniziale identico, con l’aggiornamento in useEffect. I casi sono in hydration failed: UI iniziale diversa.
Su un catalogo di 111.985 componenti e 1.556 blocchi, misurato il 1 novembre 2026 su ui.eneawork.it, la build tratta ogni pagina come un confine: un errore in un solo client component ferma tutto il deploy. Non esiste una build parziale.
Dove guardare nel codice
Tre record utili quando l’errore è a runtime invece che in build: le error page, i pattern di gestione errore, e i blocchi form quando l’errore nasce da un handler.
Un 404 dopo il deploy non è un errore di build ma si confonde facilmente: la sequenza è in 404 dopo il deploy. La scelta tra action e route che evita metà di questi errori è in Route Handler o Server Action. Il confronto tra framework con lo stesso albero di build è in Astro vs Next.js.
Il limite da mettere in conto
Nessuno di questi errori si riproduce in sviluppo con lo stesso ordine di valutazione della build. Un npm run build locale prima del push è l’unico test che li anticipa.
Domande frequenti
Perché la build fallisce e lo sviluppo no?
Perché la build valuta tutti i moduli con regole più rigide e senza <code>window</code>. Un modulo che funziona nel browser può non avere senso sul server: il confine è il problema, non il comando.
window is not defined si risolve con typeof?
È una toppa: <code>typeof window</code> evita l’errore ma non evita il markup diverso tra server e client. Meglio <code>useEffect</code> o <code>dynamic</code> con <code>ssr: false</code>.
Dove va "use client"?
Sul componente più basso che ha stato, eventi o hook. Metterlo in alto rende client tutto il sottoalbero e perdi il rendering server.
Posso passare una funzione a un client component?
No, le funzioni non sono serializzabili. Passa un dato e gestisci l’azione con una Server Action, oppure definisci la funzione dentro il client component.
Come evito gli errori di hydration?
Rendendo il primo render identico sui due lati: niente data corrente, niente valori casuali, niente accesso a <code>localStorage</code> durante il render. Aggiorna dopo il montaggio.