Tooling7 min read

Monorepo Architecture

Structure multi-package monorepos with Turborepo or Nx: shared packages, task pipelines, caching, and consistent tooling across all apps.

monorepoturboreponxdevops

A monorepo hosts multiple applications and packages in a single repository. When done well, it enables code sharing, atomic cross-package changes, and unified tooling. Turborepo and Nx provide intelligent caching and task orchestration to make monorepos fast.

Use Turborepo for JS/TS monorepos (simpler config). Use Nx when you need more powerful code generation and affected detection. Share packages via workspace protocols, not npm publish. Cache aggressively - remote caching transforms CI speed.

Workflow

  1. Bootstrap with TurborepoTurborepo's pipeline system and caching eliminate redundant builds across packages.
  2. Design shared packages correctlyPoorly structured shared packages create circular dependencies or deployment coupling.
  3. Configure TypeScript paths for cross-package importsTypeScript needs to resolve `@acme/ui` to local source, not node_modules during development.
  4. Enable remote cachingWithout remote caching, CI builds every package from scratch on every run - remote cache shares build artifacts across CI machines.
  5. Structure CI for monorepo efficiencyRunning all tests on every PR regardless of what changed wastes CI minutes and slows feedback.

Output Format

Turborepo workspace with apps/packages structure, pipeline config, remote caching enabled, CI running only affected packages, shared TypeScript and ESLint configs.

Examples

turbo.json pipeline: `{ "pipeline": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**", ".next/**"] }, "test": { "dependsOn": ["build"] }, "dev": { "cache": false, "persistent": true } } }`
Shared package: `// packages/ui/package.json
{ "name": "@acme/ui", "main": "./src/index.ts", "exports": { ".": "./src/index.ts" } }`
Affected CI: `- run: npx turbo run build test lint --filter=...[HEAD^1] --token=$TURBO_TOKEN --team=$TURBO_TEAM`

Edge Cases & Gotchas

  • Circular dependencies between packages: enforce with eslint-plugin-import `no-cycle` rule; use dependency injection to break cycles
  • Different Node versions per app: use `.nvmrc` per app directory and switch in CI; Turborepo doesn't manage Node versions
  • Docker builds in monorepo: use `turbo prune --scope=app-name` to create a minimal subset of the monorepo with only what the target app needs

When to Break These Rules

  • Small teams (<5 engineers) with 1-2 apps: separate repositories are simpler. Add monorepo when you have >3 applications sharing code or when cross-package atomic changes are frequent.

Reference Files

  • Turborepo docs: https://turbo.build/repo/docs
  • Nx docs: https://nx.dev/
  • pnpm workspaces: https://pnpm.io/workspaces
  • Changesets for versioning: https://github.com/changesets/changesets

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.