Back to blog

2026-05-29

Monorepos for a small SaaS: when they pay off and what to skip

When pnpm workspaces (and optional Turborepo) help a single product team and what not to configure on day one.

A monorepo (pnpm workspace:*, optionally Turborepo on top) centralizes tooling pins. For one SaaS with 1–3 deployables it can still be overkill if you split packages before the boundaries are real.

Technical refs: pnpm workspaces, Turborepo caching.

When it actually helps

  • You ship multiple artifacts (api + web) that share TS types, Zod schemas, or generated clients.
  • CI burns minutes on duplicate installs / non-cached builds—remote cache or turbo run pays off.
  • You must coordinate breaking API changes with consumers in one revision.

Skip on day one

  • Empty packages (“future-proof” @acme/types with no consumers).
  • Turbo pipelines fanning lint across unrelated folders with near-zero cache hits.
  • Independent semver for internal libs if you always deploy one atomic release—prefer workspace:* + git tags.

Turbo cache ROI

Remote cache wins when:

  • Tasks are deterministic (same inputs → same outputs).
  • outputs in turbo.json are accurate—otherwise you get poisonous hits.

For 1–2 dev teams, sometimes pnpm --filter web build + CI artifact cache is enough without Turbo.

Minimal turbo.json (commented)

Reasonable starting point for two packages web and api that emit dist (rename to match your package.json names):

{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".output/**"]
    },
    "lint": {
      "outputs": []
    },
    "test": {
      "dependsOn": ["^build"],
      "outputs": ["coverage/**"]
    }
  }
}
  • dependsOn: ["^build"] — build upstream workspace deps first (shared types, etc.).
  • outputs — honest list of generated dirs; if wrong, remote cache silently restores stale artifacts.

Typical local run: pnpm turbo run build --filter=web or turbo run build from the root when every package should participate.

Minimal CI

Typical matrix:

  1. pnpm install --frozen-lockfile
  2. pnpm lint + pnpm test (parallel friendly)
  3. pnpm build optionally scoped via --filter to changed packages (git diff scripts)

Avoid duplicating identical jobs per micro-package when total build time stays under a few minutes.

Remote caching and Turbo

If you adopt Turborepo, the inflection point is usually remote caching: reuse dist/ artifacts and task outputs across CI machines instead of rebuilding cold every time. Without remote cache, Turbo is still a solid local orchestrator but barely moves CI minutes. Declare outputs in turbo.json honestly—lying to the cache poisons builds silently.

When to stay monolithic

Signals:

  • You spend more time bumping internal versions than shipping features.
  • Cross-package debugging hurts more than the structural win.

A tidy monolith with folders (apps/web, services/api) and one or two package roots is perfectly professional until deploy boundaries diverge.

Summary

Adopt the monorepo when deploy boundaries + real shared code exist. Otherwise keep a disciplined monolith—less tooling, same rigor.

Want to ship ideas like these into your product?

Share context, constraints, and goals. We will tell you if partnering makes sense and how to frame the first step.