Topics
v6.0.0
On this page · sections

Monorepo Build Pipeline

A monorepo keeps shared UI, config, and types in one place — one PR can update a button and every consumer. Interviewers test task graphs, caching, affected CI, and publish workflows.

ToolFocusBest for
WorkspacesLink local packagesworkspace:* dependencies
TurborepoTask runner + cacheLean config, Vercel remote cache
NxFull dev platformGenerators, boundaries, enterprise governance
ChangesetsVersion + publishSemver for internal libraries

Why Monorepo — Turborepo vs Nx

Definition

{ "name": "acme", "private": true, "workspaces": ["apps/*", "packages/*"], "scripts": { "build": "turbo run build", "test": "turbo run test" } }

Three wins:

  • Atomic cross-package changes — one PR updates API and all consumers
  • Shared tooling once — ESLint, TypeScript, CI at root
  • Cacheable task graphs — skip unchanged work via hashing

Turborepo — lean task runner + remote cache. Nx — project graph, generators, module boundaries, plugin ecosystem.

pnpm content-addressable storage and strict symlinks — common pairing; fewer phantom dependencies than npm.

Polyrepo still correct when teams are fully independent with semver-published packages and no overlapping release trains.

Prevent circular deps: apps → packages, never packages → apps. Nx @nx/enforce-module-boundaries or ESLint import/no-restricted-paths.

Interview: Monorepo ≠ one giant bundle — each package still builds independently.

Task Graph & dependsOn

Definition

{ "$schema": "https://turbo.build/schema.json", "tasks": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**", ".next/**"] }, "test": { "dependsOn": ["build"], "outputs": [] }, "lint": { "dependsOn": ["^build"], "outputs": [] } } }
turbo run build order: 1. packages/ui build (no deps) 2. packages/utils build 3. apps/web build (waits for ^build)
  • dependsOn: ["^build"] — run build in workspace dependencies first (caret = upstream)
  • dependsOn: ["build"] — run build in the same package first
  • outputs — folders to hash and cache
  • inputs — globs that invalidate cache

Without ^build, apps compile against stale @acme/ui dist. Persistent tasks (dev, watch) opt out of caching.

turbo run build --filter=@acme/web builds that package and dependency subgraph.

Interview: dependsOn: ["build"] = same package. dependsOn: ["^build"] = dependency packages first.

Local & Remote Cache

Definition

Turborepo hashes task name, package, inputs, environment variables in env/globalEnv, and dependency task outputs. Same hash = cache hit.

{ "globalDependencies": ["pnpm-lock.yaml", "tsconfig.base.json"], "globalEnv": ["NODE_ENV", "CI"], "tasks": { "build": { "env": ["NEXT_PUBLIC_API_URL"], "inputs": ["src/**", "package.json", "tsconfig.json"], "outputs": ["dist/**"] } } }
  • Local cache — .turbo/cache
  • Remote cache — Vercel, S3, Nx Cloud — CI agents reuse prior builds
  • globalDependencies — root files invalidating everything

Never cache non-deterministic tasks (deploy, live API e2e). Candidates forget env vars in the hash — wrong cache hits when API_URL changes unlisted.

Missing outputs → cache hits return empty folders — silent broken builds.

Interview: Below 60% cache hit rate usually means misconfigured inputs or over-broad globalDependencies.

Affected-Only CI

Definition

# .github/workflows/ci.yml — simplified jobs: build: steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - run: pnpm install --frozen-lockfile - run: pnpm turbo run lint test build --filter=...[origin/main]
Changed: packages/ui/src/Button.tsx Affected: packages/ui, apps/web, apps/admin (dependents) NOT run: packages/utils (unchanged, no downstream path)
  • Git base — compare PR branch to origin/main
  • Turborepo — --filter=...[origin/main] = changed + downstream dependents
  • Nx — nx affected -t build --base=origin/main --head=HEAD
  • Full run on main — affected on PRs; complete graph on main merge

fetch-depth: 0 required — shallow checkout cannot diff against main. Lockfile change hits globalDependencies — full rebuild intentional.

Draw blast radius: changed package → direct dependents → apps importing them.

Interview: Affected only on changed package is wrong — must include transitive dependents.

Changesets & Package Versioning

Definition

Internal packages consumed by other teams need semver, not “just use main.”

--- "@acme/ui": minor "@acme/utils": patch --- Add Button loading state. Fix debounce zero-delay edge case.
Developer → pnpm changeset → commit .changeset/*.md Merge to main → bot opens "Version packages" PR Merge version PR → CI runs changeset publish

Changesets collects bump intent per package. changeset version bumps versions and changelogs. changeset publish publishes where version ≠ registry.

Apps using workspace:* update on version PR. External consumers get semver releases. Apps are usually private: true — deploy as artifacts, not npm publish.

Don’t say “we publish every commit.” Explain changeset → version PR → publish.

Interview: Required changeset policy on library PRs prevents version drift across teams.

Senior Interview Summary

Monorepo wins

Atomic changes, shared tooling, cacheable DAG. Coordination cost scales with package count.

Task graph

^build = upstream first. Parallel where graph allows. Persistent tasks not cacheable.

Cache key

inputs + env + dep output hashes. List env vars explicitly or get wrong hits.

Affected CI

--filter=...[origin/main] + dependents. Full graph on main merge.

Changesets

Intent file → version PR → publish. Semver propagates through dependents.

Best mental model

Workspaces → link packages locally Turbo/Nx → orchestrate + cache task DAG Changesets → semver for publishable libs

Practical rules

Lockfile in globalDependencies → dep bumps invalidate all Include outputs in turbo.json → or cache restores empty dist Nightly full graph + PR affected → safety vs speed balance

What to say in interviews

Design CI for a design system + three apps: explain dependsOn: ["^build"], remote cache, affected filter, and Changesets publish flow. Mention when to split monorepo back to packages + federation.

Last updated on