Concepts

Monorepos

Code organization strategy where multiple projects coexist in a single repository, sharing dependencies, configuration, and build tooling.

evergreen#architecture#tooling#dx#git

A monorepo is a single repository containing multiple distinct projects with well-defined relationships between them. It's not just "all code in one folder" — it's an architectural strategy backed by specialized tooling.

What problem it solves

In a polyrepo model (one repository per project), teams face:

  • Dependency hell — syncing versions across repos is manual and error-prone
  • Impossible atomic changes — a refactor touching 3 repos requires 3 coordinated PRs
  • Configuration duplication — ESLint, TypeScript, CI/CD copied into every repo
  • Difficult discovery — where is the code I need? What version does the other team use?

The monorepo centralizes everything in one place with tools that manage the complexity.

When to use it

Good fit when:

  • Multiple packages share common code (types, utilities, components)
  • The team needs atomic changes that cross boundaries
  • You want a single source of truth for configuration (linting, testing, CI)
  • Projects have coordinated release cycles

Avoid when:

  • Projects are completely independent with no shared code
  • Teams have very different release cycles
  • Strict access restrictions are required (monorepos imply shared visibility)
  • The repo would grow to sizes Git can't handle well (>10GB, millions of files)

Main tools

ToolFocusLanguageKey features
TurborepoBuild cachingJS/TSRemote cache, declarative pipelines, zero-config
NxFull-featuredJS/TSGenerators, affected commands, graph visualization
pnpm workspacesPackage managerJS/TSEfficient symlinks, strict dependencies
LernaPublishingJS/TSCoordinated versioning, changelogs (now part of Nx)
BazelHermetic buildsPolyglotReproducibility, extreme scalability (Google-scale)
PantsHermetic buildsPython/Go/JavaSimilar to Bazel, better DX
RushEnterpriseJS/TSStrict policies, phantom dependencies detection

Turborepo vs Nx

The most common comparison in the JavaScript ecosystem:

Turborepo — minimalist, integrates with your existing setup, excellent remote cache with Vercel. Ideal if you already have a monorepo and want to speed up builds without changing much.

Nx — more opinionated, includes generators for scaffolding, framework-specific plugins, dependency visualization. Better if starting from scratch or wanting guided structure.

Typical structure

monorepo/
├── apps/
│   ├── web/          # Next.js app
│   ├── mobile/       # React Native app
│   └── api/          # Backend service
├── packages/
│   ├── ui/           # Shared components
│   ├── utils/        # Shared utilities
│   └── config/       # Shared ESLint, TS configs
├── turbo.json        # Pipeline configuration
├── pnpm-workspace.yaml
└── package.json

Key patterns

1. Internal packages

Packages that are never published to npm — only consumed within the monorepo:

{
  "name": "@repo/ui",
  "private": true,
  "exports": {
    ".": "./src/index.ts"
  }
}

2. Task pipelines

Define dependencies between tasks for correct parallelization:

{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "test": {
      "dependsOn": ["build"]
    }
  }
}

3. Remote caching

A package build that hasn't changed is retrieved from cache instead of re-executing:

# First time: runs build
turbo build  # 45s
 
# Second time (no changes): retrieved from cache
turbo build  # 0.3s

Anti-patterns

  • Monolith in disguise — everything in one repo but no clear boundaries between packages
  • Cache without proper invalidation — builds that don't detect changes in dependencies
  • Circular dependencies — package A imports B which imports A
  • God package — a @repo/utils that grows unchecked and everything depends on

This site is a monorepo

jonmatum.com uses Turborepo + pnpm workspaces with apps/web (Next.js) and packages/knowledge (content pipeline). Turborepo's cache reduces the build from ~45s to under 1s when only MDX content changes.

Why it matters

The decision between monorepo and polyrepo affects development velocity, CI/CD complexity, and the ability to share code across teams. There is no universal answer — but understanding the trade-offs prevents months of painful migration when the initial choice doesn't scale.

References

Concepts