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

RisorsaDovePerché
Token e primitive UIpackages/uiUn solo posto da aggiornare
Config ESLint e tsconfigpackages/configLe app ereditano, non copiano
Logica di dominio condivisapackages/libFunzioni pure, testabili da sole
Route e layoutapps/web/appSono 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 standalone va 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.