2026-05-29
Monorepo per una SaaS piccola: soglia utile e cosa evitare
Quando pnpm workspace (e opzionale Turborepo) ripaga per un solo prodotto e cosa non configurare al primo giorno.
Un monorepo (pnpm workspace:*, oppure Turborepo sopra pnpm/npm/yarn) centralizza tooling e versioni. Per una SaaS con 1–3 servizi, può anche essere overhead se lo adotti prima che il confine tra pacchetti sia chiaro.
Riferimenti tecnici: pnpm workspaces, Turborepo caching.
Segnali che serve davvero
- Hai due artefatti deployati distinti (es.
api+web) che condividono tipi TS, validatori Zod, o client generato. - La CI impiega minuti a causa di install duplicati e build senza cache—Turbo remote cache o
turbo runlocale ripaga. - Serve versionare insieme breaking change su contratto API e consumer.
Cosa non fare al giorno 1
- 10 pacchetti vuoti (
@acme/types-empty) solo per “preparare il futuro”. - Pipeline Turbo che orchestrano linter su cartelle senza dipendenze reali—paga complessità senza hit di cache.
- Versioning indipendente per package interni se rilasci un solo deploy unitario—usa
workspace:*e un tag git.
Turbo cache: cosa si ripaga
La cache remota aiuta quando:
- I task sono deterministici (stessi input file → stesso output).
- Gli
outputsinturbo.jsonsono dichiarati correttamente—altrimenti cache hit “sporca”.
Per team di 1–2 dev, a volte pnpm --filter web build + artifact caching CI basta senza Turbo.
turbo.json minimale (commentato)
Partenza sensata per due package web e api che producono cartelle dist (adatta i nomi ai tuoi package.json):
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".output/**"]
},
"lint": {
"outputs": []
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"]
}
}
}
dependsOn:["^build"]— prima builda le dipendenze workspace upstream (tipi condivisi, ecc.).outputs— elenco onesto delle directory generate; se sbagli, la remote cache ti “ripristina” artefatti vecchi senza errori chiari.
Esecuzione locale tipica: pnpm turbo run build --filter=web oppure turbo run build dalla root se tutti i package devono partecipare.
CI minimale
Matrix tipico:
pnpm install --frozen-lockfilepnpm lintepnpm test(parallelizzabili)pnpm buildcon filtro--filtersui pacchetti toccati dagit diff(opzionale, richiede script)
Evita N× job uguali per ogni micro-package se il build time totale è sotto i minuti.
Remote cache e Turbo
Se adotti Turborepo, il salto qualità arriva quando abiliti remote caching: artefatti dist/ e output di test ripresi da CI/CD invece che ricompilati su ogni runner. Senza cache remota, Turbo resta un buon orchestratore locale ma non moltiplica il valore in pipeline grandi. Configura outputs in turbo.json in modo onesto, altrimenti i cache hit risultano inconsistenti senza errori evidenti.
Quando tornare (o non partire) dal monolite
Segnali:
- Passi più tempo a sincronizzare versioni tra package che a feature.
- Debugging source map cross-package ti rallenta più del guadagno.
Un monolite con cartelle (apps/web, services/api) dentro un unico package.json o due root ben definiti è perfettamente professionale finché non hai confini deploy diversi.
Sintesi
Adotta monorepo quando hai confini deploy + codice condiviso reale. Altrimenti resta monolite ordinato: meno tooling, stessa disciplina.