Dependency Management¶
Best practices for managing dependencies in a Turborepo monorepo.
Core Principle: Install Where Used¶
Dependencies belong in the package that uses them, not the root.
# Good: Install in specific package
pnpm add react --filter=@repo/ui
pnpm add next --filter=web
# Avoid: Installing in root
pnpm add react -w # Only for repo-level tools!
Benefits of Local Installation¶
1. Clarity¶
Each package's package.json lists exactly what it needs:
// packages/ui/package.json
{
"dependencies": {
"react": "^18.0.0",
"class-variance-authority": "^0.7.0"
}
}
2. Flexibility¶
Different packages can use different versions when needed:
// packages/legacy-ui/package.json
{ "dependencies": { "react": "^17.0.0" } }
// packages/ui/package.json
{ "dependencies": { "react": "^18.0.0" } }
3. Better Caching¶
Installing in root changes workspace lockfile, invalidating all caches.
4. Pruning Support¶
turbo prune can remove unused dependencies for Docker images.
What Belongs in Root¶
Only repository-level tools:
// Root package.json
{
"devDependencies": {
"turbo": "latest",
"husky": "^8.0.0",
"lint-staged": "^15.0.0"
}
}
NOT application dependencies:
- react, next, express
- lodash, axios, zod
- Testing libraries (unless truly repo-wide)
Installing Dependencies¶
Single Package¶
# pnpm
pnpm add lodash --filter=@repo/utils
# npm
npm install lodash --workspace=@repo/utils
# yarn
yarn workspace @repo/utils add lodash
# bun
cd packages/utils && bun add lodash
Multiple Packages¶
# pnpm
pnpm add jest --save-dev --filter=web --filter=@repo/ui
# npm
npm install jest --save-dev --workspace=web --workspace=@repo/ui
# yarn (v2+)
yarn workspaces foreach -R --from '{web,@repo/ui}' add jest --dev
Internal Packages¶
# pnpm
pnpm add @repo/ui --filter=web
# This updates package.json:
{
"dependencies": {
"@repo/ui": "workspace:*"
}
}
Keeping Versions in Sync¶
Option 1: Tooling¶
# syncpack - Check and fix version mismatches
npx syncpack list-mismatches
npx syncpack fix-mismatches
# manypkg - Similar functionality
npx @manypkg/cli check
npx @manypkg/cli fix
# sherif - Rust-based, very fast
npx sherif
Option 2: Package Manager Commands¶
# pnpm - Update everywhere
pnpm up --recursive typescript@latest
# npm - Update in all workspaces
npm install typescript@latest --workspaces
Option 3: pnpm Catalogs (pnpm 9.5+)¶
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
catalog:
react: ^18.2.0
typescript: ^5.3.0
Internal vs External Dependencies¶
Internal (Workspace)¶
Turborepo understands these relationships and orders builds accordingly.
External (npm Registry)¶
Standard semver versioning from npm.
Peer Dependencies¶
For library packages that expect the consumer to provide dependencies:
// packages/ui/package.json
{
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"react": "^18.0.0", // For development/testing
"react-dom": "^18.0.0"
}
}
Common Issues¶
"Module not found"¶
- Check the dependency is installed in the right package
- Run
pnpm install/npm installto update lockfile - Check exports are defined in the package
Version Conflicts¶
Packages can use different versions - this is a feature, not a bug. But if you need consistency:
- Use tooling (syncpack, manypkg)
- Use pnpm catalogs
- Create a lint rule
Hoisting Issues¶
Some tools expect dependencies in specific locations. Use package manager config:
Lockfile¶
Required for:
- Reproducible builds
- Turborepo dependency analysis
- Cache correctness