As projects split from one package to many, separate repos cause divergent deps, painful code reuse, and fragmented releases. Monorepo solves these with tooling. Turborepo + pnpm delivers the best combination today.
pnpm workspace Dependency Management
Declare apps/* packages/* structure in pnpm-workspace.yaml. Root pnpm install uses hard-links so the same dep is shared across packages with strictly isolated node_modules against phantom dependencies. workspace: protocol references internal packages instantly without publishing.
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
- 'tooling/*'
# turbo.json
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"],
"cache": true
},
"typecheck": { "dependsOn": ["^build"], "outputs": [] },
"lint": { "dependsOn": ["^build"], "outputs": [] },
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"],
"inputs": ["**/*.test.*", "jest.config.*"]
},
"dev": { "cache": false, "persistent": true }
}
}
# root package.json
{
"name": "my-monorepo",
"private": true,
"packageManager": "pnpm@9.0.0",
"scripts": {
"build": "turbo build",
"typecheck": "turbo typecheck",
"test": "turbo test",
"dev": "turbo dev",
"release": "changeset version && changeset publish"
},
"devDependencies": {
"turbo": "^2.0.0",
"@changesets/cli": "^2.27.0",
"typescript": "^5.4.0"
}
}
# packages/ui/package.json
{
"name": "@company/ui",
"version": "1.2.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
"./styles.css": "./dist/styles.css"
},
"sideEffects": false
}
Turborepo Incremental Builds and Caching
turbo.json declares task DAG: child build depends on upstream ^build, topologically sorted automatically. Per-task input hash hits local or remote cache to skip entire builds - CI speedup typically 5-20x.
| Dimension | Manual repos | Lerna + yarn | pnpm + Turborepo | pnpm + Nx |
|---|---|---|---|---|
| Deduped deps | Per-repo copies | Medium hoisting | Hardlink shared | Same as Turborepo |
| Task orchestration | Scripts | Weak lerna run | DAG smart cache | targetDefaults |
| Remote cache | None | Paid enterprise | Self-host S3/Vercel free | Nx Cloud free tier |
| Phantom dep risk | Low | High | Very Low | Very Low |
Best Practices
Standardize internal packages on @scope/pkg naming. Use Changesets for versioning/changelogs. Never cross-package relative imports. Wire remote cache to team S3 bucket so every CI run shares cache globally.