Monorepo Build Tools Guide — Nx and Turborepo for JavaScript and TypeScript
In this tutorial, you'll learn about Monorepo Build Tools Guide. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Monorepo build tools like Nx and Turborepo manage multiple packages and applications within a single Repository by providing dependency graph analysis, parallel task execution, intelligent caching, and affected-project detection to optimize build and test pipelines.
What You'll Learn
You'll learn how to set up Nx and Turborepo for JavaScript and TypeScript monorepos, configure task pipelines with caching, use affected commands to run only what changed, and optimize CI pipelines for monorepo builds at scale.
Why Monorepo Build Tools Matter
Monorepos grow to hundreds or thousands of packages. Without build tools, running tests and builds for the entire Repository wastes hours per CI run. Nx and Turborepo solve this by computing the dependency graph, detecting affected projects, caching outputs, and running only necessary tasks in parallel.
Real-World Use
The Doda Browser monorepo contains 87 packages (shared utilities, UI components, browser extensions, and backend services). Nx-powered CI pipelines run only affected tests on each Pull Request, reducing average CI time from 68 minutes to under 4 minutes.
Prerequisites
- JavaScript or TypeScript experience
- Node.js 18+ and npm/pnpm/yarn
- Familiarity with Git branching
Step 1: Nx Project Setup
Nx is a build system with first-class monorepo support, offering generators, executors, and computation caching.
# Create a new Nx monorepo
npx create-nx-workspace@latest myorg --preset=ts
cd myorg
# Generate an application and a library
nx g @nx/next:app webapp
nx g @nx/react:lib shared-ui
nx g @nx/node:lib api-client
// nx.json — Nx configuration
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"tasksRunnerOptions": {
"default": {
"runner": "nx-cloud",
"options": {
"cacheableOperations": ["build", "test", "lint"],
"accessToken": "your-nx-cloud-token"
}
}
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
},
"test": {
"inputs": ["default", "^production", "{workspaceRoot}/jest.config.ts"]
}
}
}
Expected output: Running nx graph opens a browser visualization of the project dependency graph. Running nx build webapp builds the webapp and all its dependencies in the correct order.
Step 2: Affected Commands and CI
Nx's affected commands determine which projects changed based on Git history and run tasks only for those projects.
# Run tests only for affected projects
nx affected:test --base=main --head=HEAD
# Build only affected projects and their dependencies
nx affected:build --base=main --head=HEAD
# Lint affected projects
nx affected:lint --base=main --head=HEAD
# Run multiple targets for affected projects
nx affected --target=test,lint,build --base=main --head=HEAD
# Nx can also detect uncommitted changes
nx affected:test --files="libs/shared-ui/src/button.tsx"
# .github/workflows/ci.yml
name: Monorepo CI
on:
pull_request:
jobs:
affected:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
- run: npm ci
# Only run tasks affected by the PR changes
- run: npx nx affected -t build test lint --base=origin/main
Expected behavior: If a Pull Request changes only libs/shared-ui, Nx runs build, test, and lint only for shared-ui and any packages that depend on it — not the entire monorepo.
Step 3: Turborepo Setup
Turborepo is a simpler monorepo orchestrator focused on task scheduling and caching.
# Create a Turborepo project
npx create-turbo@latest my-turbo-app
cd my-turbo-app
// turbo.json — Turborepo configuration
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [".env"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"],
"cache": true
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"],
"cache": true
},
"lint": {
"cache": true
},
"dev": {
"cache": false,
"persistent": true
}
}
}
# Run all tasks with caching
npx turbo run build test lint
# Run only affected packages (Turborepo 2+)
npx turbo run build --filter=[origin/main]
# Clear cache
npx turbo daemon clean
Expected output: First turbo run build compiles everything. Subsequent runs (with no changes) complete in milliseconds by reading cached outputs. Cache is stored locally in node_modules/.cache/turbo.
Step 4: Comparison and Migration Guide
Both tools share core features but differ in philosophy and advanced capabilities.
// Nx project.json for a library
{
"name": "shared-ui",
"targets": {
"build": {
"executor": "@nx/vite:build",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/shared-ui"
}
},
"test": {
"executor": "@nx/jest:jest",
"options": {
"jestConfig": "libs/shared-ui/jest.config.ts"
}
}
}
}
# Migration from Turborepo to Nx or vice versa
# Turborepo → Nx: add nx.json and install @nx/workspace
# Nx → Turborepo: create turbo.json and remove nx.json
# Key differences:
# Nx: generators, executors, dependency graph visualization, Nx Cloud
# Turborepo: simpler setup, lighter CLI, Vercel integration
Capability comparison:
| Feature | Nx | Turborepo |
|---|---|---|
| Task Orchestration | Yes | Yes |
| Local caching | Built-in | Built-in |
| Remote caching | Nx Cloud | Vercel Remote Cache |
| Affected commands | Native | via --filter (2.x) |
| Code generators | Extensive | None |
| Dependency graph | Built-in viewer | CLI only |
| Plugin ecosystem | Extensive | Minimal |
| CI optimization | Distributed task execution | Parallel pipeline |
Expected insight: Choose Nx for large monorepos with many frameworks needing generators and advanced dependency management. Choose Turborepo for simpler monorepos where you mainly need task scheduling and caching.
Architecture
flowchart LR
subgraph "Monorepo Packages"
A[apps/webapp]
B[apps/api]
C[libs/shared-ui]
D[libs/api-client]
E[libs/utils]
end
subgraph "Build Tool"
GRAPH[Dependency Graph]
CACHE[Computation Cache]
SCHED[Task Scheduler]
AFFECT[Affected Detection]
end
subgraph "Tasks"
BUILD[Build]
TEST[Test]
LINT[Lint]
DEPLOY[Deploy]
end
A -->|depends on| C
A -->|depends on| D
D -->|depends on| E
C -->|depends on| E
B -->|depends on| D
GRAPH --> SCHED
AFFECT --> SCHED
SCHED --> CACHE
SCHED --> BUILD
SCHED --> TEST
SCHED --> LINT
SCHED --> DEPLOY
Common Errors
1. Nx: Cannot find module '@nx/workspace'
The Nx plugin is not installed. Run npm install -D @nx/workspace or use nx init to install the correct plugins for your project structure.
2. Turborepo: No cache found for task build
First-time builds always run from scratch. Cache is populated on subsequent runs. Check that outputs in turbo.json correctly identifies build output directories.
3. Affected command detects unchanged packages
Git merge-base is incorrect. Ensure fetch-depth: 0 in CI for full Git history. On local machines, run git fetch origin main before nx affected:test --base=origin/main.
4. Circular dependency detected Package A depends on B, and B depends on A (directly or transitively). Restructure to remove cycles — extract shared code into a third package that both A and B depend on.
5. Remote cache miss in CI
The remote cache key (computed from inputs) differs between local and CI machines. Ensure consistent Node.js versions, operating systems, and environment variables. Use env in cache configuration to include platform-specific keys.
Practice Questions
1. How does Nx determine which projects are affected? Nx compares the Git diff between the base and head commits, finds changed files, maps them to projects, and then traverses the dependency graph to find all downstream projects that depend on changed files.
2. What is the purpose of the dependsOn configuration in turbo.json?
It defines task ordering. "test": { "dependsOn": ["build"] } means the test task waits for the build task to complete first in the same package.
3. How does computation caching work in monorepo tools? Each task's inputs (source files, environment variables, dependencies) are hashed. If the same hash exists in the cache, the cached output is restored instead of re-executing the task.
4. Challenge: Set up an Nx monorepo with two applications and three shared libraries
Create a workspace with a React app and a Node API app sharing three libraries: ui-components, api-client, and utils. Verify that changing utils triggers rebuilds in both applications.
5. Challenge: Remote caching with GitHub Actions Configure Nx Cloud or Turborepo remote caching in a GitHub Actions workflow. Measure the difference in CI time between cold cache (first run) and warm cache (subsequent runs on the same branch).
FAQ
Next Steps
- Explore JavaScript module resolution in monorepo environments
- Learn how TypeScript project references work with Nx dependency graphs
- Compare monorepo strategies with the monolithic vs micro-frontends guide
- Review the Git monorepo workflows tutorial for managing large repositories
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro