Torna al blog

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 run locale 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 outputs in turbo.json sono 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:

  1. pnpm install --frozen-lockfile
  2. pnpm lint e pnpm test (parallelizzabili)
  3. pnpm build con filtro --filter sui pacchetti toccati da git 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.

Vuoi applicare idee come queste al tuo prodotto?

Raccontaci contesto, vincoli e obiettivi: ti diciamo se ha senso lavorare insieme e come impostare il primo passo.