Monorepo Architecture
Structure multi-package monorepos with Turborepo or Nx: shared packages, task pipelines, caching, and consistent tooling across all apps.
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
- Bootstrap with TurborepoTurborepo's pipeline system and caching eliminate redundant builds across packages.
- Design shared packages correctlyPoorly structured shared packages create circular dependencies or deployment coupling.
- Configure TypeScript paths for cross-package importsTypeScript needs to resolve `@acme/ui` to local source, not node_modules during development.
- Enable remote cachingWithout remote caching, CI builds every package from scratch on every run - remote cache shares build artifacts across CI machines.
- 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/docsNx docs: https://nx.dev/pnpm workspaces: https://pnpm.io/workspacesChangesets for versioning: https://github.com/changesets/changesets