Turborepo

Rule

Scope: Monorepo tasks and package setup.

Just-in-Time Packages

  • MUSTExport source TypeScript from packages via exports (no dist builds).
  • SHOULDUse source-mapped imports for better DX.
{
  "name": "@project/utils",
  "exports": {
    ".": "./src/index.ts",
    "./*": "./src/*.ts"
  }
}

Task Configuration

  • MUSTdev tasks are persistent and uncached.
  • MUSTbuild, typecheck, lint, test depend on upstream (^task).
  • SHOULDKeep turbo.json minimal — only define tasks that need configuration.

Caching

  • MUSTDeclare outputs for every task that writes to disk ("outputs": ["dist/**"]). Without this, results aren't cached.
  • MUSTInclude environment variables in env key so cache invalidates on change. Use globalEnv for variables affecting all tasks.
  • MUSTAdd .env files to inputs — Turbo doesn't auto-detect .env changes.
  • SHOULDAvoid root .env files. Place them in packages that need them for explicit dependency tracking.

Package Configuration

  • SHOULDUse package-level turbo.json with "extends": ["//"] for package-specific overrides instead of package#task entries in root config.
  • MUSTDeclare workspace dependencies in package.json ("@repo/types": "workspace:*") for ^build to trigger correctly.

CI

  • SHOULDUse turbo run build (not turbo build) in scripts and CI for reproducibility.
  • SHOULDUse --affected in CI to run only changed packages plus their dependents.
  • MUSTUse pnpm install --frozen-lockfile in CI for reproducible builds.

Anti-Patterns

NeverWhyInstead
Chain with && in root scriptsBypasses Turbo's dependency graphUse turbo run
prebuild scripts building depsDuplicates Turbo's workDeclare package dependencies
../ in inputsImplicit cross-package depsUse $TURBO_ROOT$/
--parallel flagIgnores dependency orderConfigure dependsOn
Cross-package relative importsTight couplingImport from package entry points