React
Next.js in un monorepo: la struttura che regge
Un monorepo Next.js conviene quando le app condividono componenti e configurazione. La struttura minima con pnpm, i pacchetti da estrarre e i costi reali.
In sintesi
- Un lockfile e una sola versione di React: due copie rompono gli hook a runtime.
- `apps/` contiene i deploy, `packages/` contiene codice condiviso con un nome npm.
- Il pacchetto UI va transpilato o compilato: Next non legge sorgenti fuori dalla sua cartella.
Un monorepo Next.js conviene quando due applicazioni condividono davvero componenti, token e configurazione. La struttura minima è apps/ per i deploy, packages/ per il codice condiviso e un solo lockfile alla radice.
La struttura minima
repo/
apps/
web/ # app principale, un deploy
app/
next.config.ts
package.json
docs/ # seconda app, stesso design system
packages/
ui/ # componenti condivisi
config/ # eslint, tsconfig, tailwind
lib/ # utility pure
package.json
pnpm-workspace.yaml
turbo.json
Il workspace è dichiarato in pnpm-workspace.yaml; ogni cartella sotto apps/ e packages/ ha un package.json con un nome npm. Next.js non sa di essere in un monorepo: è il resolver dei moduli a collegare i pacchetti.
Cosa condividere e cosa no
| Risorsa | Dove | Perché |
|---|---|---|
| Token e primitive UI | packages/ui | Un solo posto da aggiornare |
| Config ESLint e tsconfig | packages/config | Le app ereditano, non copiano |
| Logica di dominio condivisa | packages/lib | Funzioni pure, testabili da sole |
| Route e layout | apps/web/app | Sono URL, non codice riusabile |
Un pacchetto che esporta file TSX va elencato in transpilePackages, altrimenti la build di Next ignora il file fuori dalla sua cartella. Se il pacchetto è usato anche fuori da Next, compilalo e pubblica il risultato.
// apps/web/next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
transpilePackages: ["@repo/ui", "@repo/lib"],
};
export default nextConfig;
Il comando che smaschera il problema
# una sola versione di React in tutto il workspace
pnpm why react
# build della sola app: rivela gli import fuori dal pacchetto
pnpm --filter @repo/web build
Se pnpm why react stampa due versioni, gli hook si rompono con «Invalid hook call» a runtime e il messaggio non dice perché. La causa è un pacchetto che dichiara React come dipendenza normale invece che come peer.
Il record che uso come esempio
Il catalogo EneUI indicizza 111.985 componenti e 1.556 blocchi da 1.472 librerie (1 novembre 2026). Un blocco completo si installa come sorgente in packages/ui: dashboard-01, con la categoria dashboard e le primitive in card.
Dove il monorepo costa
- La build remota richiede un token di cache e una configurazione che nessuno mantiene.
- L'output
standaloneva copiato con il lockfile della radice, non con quello dell'app. - Un pacchetto condiviso senza test diventa un punto di regressione per tutte le app.
- L'aggiornamento di Next slitta, perché va provato su ogni app prima del merge.
Limite noto: Next non risolve un pacchetto che espone solo ESM senza campo exports. La build fallisce con un errore di sintassi che punta al file sbagliato, e la correzione sta nel package.json del pacchetto.
Per la struttura della singola app vedi struttura App Router. Per la cache che attraversa le richieste i quattro livelli di cache; il confronto tra framework per un sito marketing è in Next.js vs Vite.
Domande frequenti
Serve Turborepo per un monorepo Next.js?
No. Un workspace pnpm basta per due app e una decina di pacchetti. Turborepo aggiunge cache dei task e grafo delle dipendenze quando le build superano i cinque minuti.
Dove metto il design system?
In packages/ui. Deve avere un nome npm, un package.json e nessuna dipendenza dall'app che lo consuma.
Come evito due copie di React?
Dichiara React e React DOM come peerDependencies nei pacchetti condivisi e come dependencies solo nelle app. Poi verifica con pnpm why react.
Meglio un'app unica o più app?
Una sola app finché la condivisione riguarda solo i componenti. La seconda app si aggiunge quando ha deploy, dominio o pubblico diversi.
Il deploy su Vercel funziona senza configurazione?
Con pnpm sì, se il lockfile sta alla radice e il comando di build usa il filtro sul pacchetto. Con npm e cartelle annidate va impostata la root directory del progetto.