Dependency Management
Managing internal package dependencies, versioning strategies, and dependency graph health in monorepos
You are an expert in managing internal and external dependencies within monorepo architectures, including package linking, version alignment, and dependency graph health. ## Key Points - name: Check circular dependencies 1. **Define clear package boundaries** — Every package should have a single purpose and a clean public API exported from `index.ts`. 2. **Use peer dependencies for singletons** — React, framework stores, and other packages that must be single instances should be peer deps. 3. **Align external versions** — Use catalogs, syncpack, or Renovate grouping to prevent version drift across packages. 4. **Enforce dependency direction** — Use lint rules or Nx boundaries to prevent apps from being imported by libs, or lower-level libs from importing higher-level ones. 5. **Detect circular dependencies in CI** — Circular deps break build ordering and create subtle runtime issues. Fail CI if any are found. 6. **Use `exports` field** — Define explicit entry points to prevent consumers from reaching into internal files. 7. **Minimize cross-package dependencies** — Each additional edge in the dependency graph reduces parallelism and increases the blast radius of changes. - **Duplicated framework instances** — If React appears multiple times in the bundle, hooks break. Use peer deps and check with `npm ls react`. - **Barrel file bloat** — A top-level `index.ts` that re-exports everything forces bundlers to load the entire package. Use subpath exports for large packages. - **Version mismatches in lockfile** — Different packages requesting different ranges of the same dep can cause multiple installed versions. Use syncpack to detect this. - **Forgetting to build dependencies first** — If `@myorg/ui` depends on `@myorg/utils`, building `ui` before `utils` fails. Configure your task runner's topological ordering. ## Quick Example ```bash npx syncpack list-mismatches # Find version mismatches npx syncpack fix-mismatches # Align all to the highest version npx syncpack set-semver-ranges # Normalize range format ``` ```bash # Using madge npx madge --circular --extensions ts,tsx packages/ # Using dpdm npx dpdm --circular --tree false packages/core/src/index.ts ```
skilldb get monorepo-skills/Dependency ManagementFull skill: 311 linesDependency Management — Monorepo Management
You are an expert in managing internal and external dependencies within monorepo architectures, including package linking, version alignment, and dependency graph health.
Core Philosophy
Overview
Dependency management in a monorepo involves coordinating how packages reference each other (internal dependencies), how external dependency versions are aligned across packages, and how the dependency graph stays clean and acyclic. Good dependency management is the foundation of a healthy monorepo: it enables caching, parallel builds, and independent deployability.
Setup & Configuration
Internal Package Structure
packages/
core/ # No internal deps — leaf package
utils/ # Depends on core
ui/ # Depends on core, utils
feature-auth/ # Depends on ui, utils
apps/
web/ # Depends on ui, feature-auth
api/ # Depends on core, utils
Declaring Internal Dependencies
With pnpm workspaces:
{
"name": "@myorg/ui",
"dependencies": {
"@myorg/core": "workspace:*",
"@myorg/utils": "workspace:*"
}
}
With npm/Yarn workspaces:
{
"name": "@myorg/ui",
"dependencies": {
"@myorg/core": "*",
"@myorg/utils": "*"
}
}
Package Exports
Every internal package should define clear entry points:
{
"name": "@myorg/utils",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"./math": {
"import": "./dist/math.mjs",
"require": "./dist/math.js",
"types": "./dist/math.d.ts"
}
}
}
TypeScript Project References
For type-safe internal dependencies without building:
// apps/web/tsconfig.json
{
"extends": "@myorg/typescript-config/react.json",
"compilerOptions": {
"composite": true
},
"references": [
{ "path": "../../packages/ui" },
{ "path": "../../packages/utils" }
]
}
Core Patterns
Development-Time Linking (Source Imports)
For faster dev loops, point to source instead of built output:
{
"name": "@myorg/ui",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
}
},
"publishConfig": {
"main": "./dist/index.js",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
}
}
}
}
With bundler aliases (e.g., Next.js):
// next.config.js
const path = require('path');
module.exports = {
transpilePackages: ['@myorg/ui', '@myorg/utils'],
};
External Dependency Version Alignment
Using pnpm catalogs (pnpm 9+):
# pnpm-workspace.yaml
catalog:
react: ^18.3.0
react-dom: ^18.3.0
zod: ^3.23.0
typescript: ^5.5.0
Using syncpack:
npx syncpack list-mismatches # Find version mismatches
npx syncpack fix-mismatches # Align all to the highest version
npx syncpack set-semver-ranges # Normalize range format
.syncpackrc.json:
{
"versionGroups": [
{
"label": "Use workspace protocol for internal packages",
"packages": ["@myorg/**"],
"dependencies": ["@myorg/**"],
"dependencyTypes": ["prod", "dev"],
"pinVersion": "workspace:*"
},
{
"label": "Pin React version across all packages",
"dependencies": ["react", "react-dom"],
"pinVersion": "^18.3.0"
}
]
}
Using Renovate with group rules:
{
"packageRules": [
{
"groupName": "react",
"matchPackageNames": ["react", "react-dom", "@types/react", "@types/react-dom"],
"matchUpdateTypes": ["major", "minor", "patch"]
},
{
"groupName": "testing",
"matchPackageNames": ["vitest", "@testing-library/*"],
"matchUpdateTypes": ["minor", "patch"]
}
]
}
Dependency Graph Validation
With Nx module boundaries:
{
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"depConstraints": [
{ "sourceTag": "type:app", "onlyDependOnLibsWithTags": ["type:feature", "type:ui", "type:util"] },
{ "sourceTag": "type:feature", "onlyDependOnLibsWithTags": ["type:ui", "type:util"] },
{ "sourceTag": "type:ui", "onlyDependOnLibsWithTags": ["type:util"] },
{ "sourceTag": "type:util", "onlyDependOnLibsWithTags": ["type:util"] }
]
}
]
}
}
With eslint-plugin-import for barrel file control:
// Prevent deep imports into internal modules
'import/no-internal-modules': ['error', {
allow: ['@myorg/*/src/**']
}],
'no-restricted-imports': ['error', {
patterns: [{
group: ['@myorg/*/src/*'],
message: 'Import from package root, not internal paths.'
}]
}],
Peer Dependencies for Shared Singletons
Packages that must share a single instance (React, Zustand stores):
{
"name": "@myorg/ui",
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
},
"devDependencies": {
"react": "^18.3.0",
"react-dom": "^18.3.0"
}
}
Circular Dependency Detection
# Using madge
npx madge --circular --extensions ts,tsx packages/
# Using dpdm
npx dpdm --circular --tree false packages/core/src/index.ts
Add to CI:
- name: Check circular dependencies
run: npx madge --circular --extensions ts,tsx packages/
Best Practices
- Define clear package boundaries — Every package should have a single purpose and a clean public API exported from
index.ts. - Use peer dependencies for singletons — React, framework stores, and other packages that must be single instances should be peer deps.
- Align external versions — Use catalogs, syncpack, or Renovate grouping to prevent version drift across packages.
- Enforce dependency direction — Use lint rules or Nx boundaries to prevent apps from being imported by libs, or lower-level libs from importing higher-level ones.
- Detect circular dependencies in CI — Circular deps break build ordering and create subtle runtime issues. Fail CI if any are found.
- Use
exportsfield — Define explicit entry points to prevent consumers from reaching into internal files. - Minimize cross-package dependencies — Each additional edge in the dependency graph reduces parallelism and increases the blast radius of changes.
Common Pitfalls
- Importing from
src/across packages — Always import from the package name (@myorg/utils), never from relative paths to source. UsetranspilePackagesif you need source-level imports in dev. - Duplicated framework instances — If React appears multiple times in the bundle, hooks break. Use peer deps and check with
npm ls react. - Barrel file bloat — A top-level
index.tsthat re-exports everything forces bundlers to load the entire package. Use subpath exports for large packages. - Version mismatches in lockfile — Different packages requesting different ranges of the same dep can cause multiple installed versions. Use syncpack to detect this.
- Forgetting to build dependencies first — If
@myorg/uidepends on@myorg/utils, buildinguibeforeutilsfails. Configure your task runner's topological ordering.
Anti-Patterns
Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.
Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.
Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.
Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.
Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.
Install this skill directly: skilldb add monorepo-skills
Related Skills
Changesets
Versioning and changelog management with Changesets for coordinated monorepo package releases
CI Optimization
CI/CD optimization for monorepos including affected detection, caching strategies, and parallel execution
Lerna
Multi-package repository management with Lerna for versioning, publishing, and task orchestration
Nx
Monorepo development with Nx including project graph, generators, executors, and computation caching
Pnpm Workspaces
Managing monorepo packages with pnpm workspaces including linking, filtering, and dependency hoisting
Shared Configs
Sharing ESLint, TypeScript, Prettier, and other tool configurations across monorepo packages