Turborepo Monorepo Guide: Managing Multi-App Projects
Monorepos containing multiple applications and shared packages are now the standard architecture for companies building interconnected software products. Turborepo, acquired by Vercel, provides the build orchestration layer that makes monorepos practical -- handling task dependencies, caching, and parallel execution across dozens of packages.
Key Takeaways
- Turborepo remote caching can reduce CI build times by 80-90% on incremental changes
- Task pipelines ensure packages build in the correct dependency order automatically
- Shared packages (types, validators, utilities) eliminate code duplication across apps
- pnpm workspaces provide disk-efficient dependency management for monorepos
Monorepo Architecture
Directory Structure
A typical Turborepo monorepo separates applications from shared packages:
my-monorepo/
apps/
api/ # NestJS backend
web/ # Next.js frontend
docs/ # Documentation site
mobile/ # React Native app
packages/
db/ # Drizzle ORM schemas and migrations
types/ # Shared TypeScript types
validators/ # Zod validation schemas
utils/ # Shared utility functions
ui/ # Shared UI components
config/ # Shared configuration (ESLint, TypeScript)
turbo.json # Turborepo configuration
pnpm-workspace.yaml
package.json # Root package.json
Workspace Configuration
Define workspaces in pnpm-workspace.yaml:
packages:
- "apps/*"
- "packages/*"
Each workspace has its own package.json with dependencies and scripts. Shared packages are referenced using the workspace protocol:
{
"dependencies": {
"@myorg/types": "workspace:*",
"@myorg/validators": "workspace:*",
"@myorg/utils": "workspace:*"
}
}
Turborepo Configuration
Task Pipeline
turbo.json defines how tasks relate to each other:
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"dev": {
"dependsOn": ["^build"],
"cache": false,
"persistent": true
},
"lint": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["build"]
}
}
}
Key concepts:
- dependsOn: ["^build"]: The caret (^) means "build all dependencies first." When building the web app, Turborepo builds packages/types, packages/validators, and packages/utils first.
- outputs: Files that Turborepo caches. On subsequent runs, if inputs have not changed, Turborepo replays the cached output instead of rebuilding.
- persistent: true: For dev servers that keep running.
- cache: false: Disable caching for tasks that must always run (like dev servers).
Shared Packages
Types Package
Centralize TypeScript types used across apps:
// packages/types/src/user.ts
export interface User {
id: string;
email: string;
name: string;
role: "admin" | "user" | "support";
createdAt: Date;
}
export type CreateUserDTO = Omit<User, "id" | "createdAt">;
export type UpdateUserDTO = Partial<CreateUserDTO>;
Validators Package
Zod schemas shared between frontend and backend:
// packages/validators/src/user.ts
import { z } from "zod";
export const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(255),
role: z.enum(["admin", "user", "support"]),
});
Build Configuration
Shared packages must be pre-built before apps can consume them. Configure each package with a build script that compiles TypeScript to JavaScript:
{
"scripts": {
"build": "tsc --project tsconfig.json",
"dev": "tsc --watch"
}
}
Caching Strategy
Local Caching
Turborepo caches task outputs in node_modules/.cache/turbo by default. When you run turbo build and nothing has changed, Turborepo replays the cached output in milliseconds.
Remote Caching
For CI/CD, enable remote caching to share build artifacts across team members and CI runs:
npx turbo login
npx turbo link
Remote caching means a build completed on one developer machine or CI run is available to everyone. The impact is dramatic: incremental CI builds drop from 10 minutes to under 1 minute.
Development Workflow
Running All Apps
pnpm dev # Starts all apps and watches all packages
Turborepo builds shared packages first (because dev dependsOn ^build), then starts app dev servers in parallel. Changes to shared packages trigger rebuilds that propagate to consuming apps.
Running Specific Apps
pnpm dev --filter=web # Only the web app and its dependencies
pnpm dev --filter=api # Only the API and its dependencies
Filtering is essential for large monorepos where starting everything is unnecessary.
CI/CD Optimization
Affected Package Detection
Turborepo determines which packages changed since the last commit and only runs tasks for affected packages:
turbo build --filter=...[HEAD^1]
This runs build only for packages that changed in the latest commit and their dependents.
Parallel Execution
Turborepo runs independent tasks in parallel. If packages/types and packages/utils have no dependency relationship, they build simultaneously. The concurrency level adapts to available CPU cores.
Frequently Asked Questions
Q: How many packages can a Turborepo monorepo handle?
Turborepo handles monorepos with hundreds of packages. Performance scales well because the task graph is analyzed at startup, and caching means most packages skip builds on incremental changes.
Q: Should we use pnpm or npm for workspaces?
pnpm is strongly recommended for monorepos. Its content-addressable store deduplicates dependencies across packages, saving significant disk space and installation time. pnpm workspaces are also more strict about dependency resolution, catching missing dependency declarations.
Q: How do we handle different Node.js versions across apps?
Use engines field in each package.json to specify Node.js version requirements. For runtime differences, configure CI to use the appropriate version per app.
Q: Can we incrementally adopt Turborepo?
Yes. Add turbo.json to an existing pnpm workspace project and configure task pipelines. Existing scripts continue to work. You gain caching and task orchestration immediately without restructuring.
What Is Next
Turborepo transforms monorepo development from a maintenance burden into a productivity multiplier. Start with a simple structure and grow as your project demands.
Contact ECOSIRE for monorepo architecture consulting, or explore our Odoo integration services for multi-app ERP architectures.
Published by ECOSIRE -- helping businesses scale with enterprise software solutions.
Written by
ECOSIRE Research and Development Team
Building enterprise-grade digital products at ECOSIRE. Sharing insights on Odoo integrations, e-commerce automation, and AI-powered business solutions.
Related Articles
Docker Odoo Deployment: Production-Ready Container Setup
Deploy Odoo in Docker containers for production. Complete guide covering Docker Compose configuration, PostgreSQL setup, Nginx reverse proxy, SSL, backups, and scaling.
Building APIs with Drizzle ORM and NestJS: Complete Guide
Build type-safe APIs with Drizzle ORM and NestJS. Learn schema definition, migrations, query building, relations, transactions, and testing patterns for production applications.
Playwright Testing Guide: E2E Tests for Next.js Applications
Write reliable end-to-end tests for Next.js applications with Playwright. Complete guide covering setup, page objects, authentication testing, visual regression, and CI/CD integration.