Prompt
Troubleshooting slow local builds in a monorepo
Latest observation
Here’s a practical checklist for troubleshooting slow local builds in a monorepo, organized from quickest wins to deeper diagnosis.
1) Identify what’s actually slow
Before changing anything, measure where the time goes:
- Dependency installation:
npm/pnpm/yarn install - TypeScript build / typecheck
- Bundling: Vite/Webpack/Rollup/Turbopack/Next
- Tests
- Task orchestration: Nx/Turborepo/Bazel/Lerna
- File watching / incremental rebuilds
- Docker / VM / remote filesystem overhead
Use timing on each command:
time pnpm build
time pnpm test
time pnpm typecheck
If you use a task runner:
nx build my-app --verboseturbo run build --dry-runbazel build //... --profile=profile.json
2) Check for missing caching
Monorepos get slow fast when caches aren’t working.
Common cache problems
- Cache directory not writable
- Cache is being cleared too often
- Build outputs aren’t declared correctly
- Tasks are non-deterministic
- Environment variables make cache keys miss
- Timestamps/randomness in outputs
What to verify
- Local task cache enabled
- Remote cache configured if applicable
- Output paths are correct
- Inputs are precise, not over-broad
Examples:
- Nx: check
outputs,inputs, and affected graph - Turborepo: check
turbo.jsonoutputs,inputs,dependsOn - Bazel: inspect action cache and sandboxing
- Webpack/Vite: confirm persistent cache is on
3) Reduce the amount of work
A monorepo can accidentally rebuild too much.
Common causes
- A change invalidates the entire repo
- Shared packages export too much
- Overly broad file globs like
**/* - TypeScript project references not set up
- Every package depends on a “common” package
Fixes
- Split large shared packages
- Use project references for TS
- Narrow watch/build inputs
- Avoid top-level wildcard dependencies
- Build only affected packages/apps
4) Optimize TypeScript
TypeScript is often the biggest local-build bottleneck.
Check for:
tsc --noEmitrunning on the whole repo- No project references
- Very large
tsconfig.jsonincludes skipLibCheckoff in large repos- Generated code included unnecessarily
Improvements
- Use incremental builds
- Use project references
- Split configs per package
- Exclude test and generated files from typecheck where possible
- Prefer
tsc -bfor referenced projects
Example tsconfig.json settings:
{
"compilerOptions": {
"incremental": true,
"composite": true,
"tsBuildInfoFile": ".tsbuildinfo",
"skipLibCheck": true
}
}
5) Optimize your package manager
Install time and dependency linking can be a major contributor.
Tips
- Prefer pnpm for large monorepos if compatible
- Avoid repeated
node_modulesdeletion - Use lockfile consistency checks
- Don’t hoist unnecessarily
- Enable store/cache reuse in CI and locally
If installs are slow:
- Check network access
- Reduce postinstall scripts
- Audit native module compilation
- Consider offline mirrors or registry caching
6) Watch out for file system bottlenecks
Local builds can be slow due to I/O, especially on:
- macOS with large repos
- Windows with antivirus scanning
- Network drives
- Docker bind mounts
- WSL accessing files across filesystem boundaries
Remedies
- Keep repo on local SSD
- Exclude repo from antivirus
- Avoid building on mounted/network volumes
- In Docker, use caching mounts and avoid bind mounts for hot paths
- On WSL, keep code inside the Linux filesystem
7) Make watching smarter
If rebuilds are slow, the watcher may be polling too much.
Signs
- CPU high while idle
- Rebuilds triggered by unrelated files
- Rapid rebuild loops
Fixes
- Use native file watchers when possible
- Exclude
dist,coverage,.turbo,.nx,node_modules - Reduce watch scope
- Increase debounce where supported
8) Inspect bundler config
Bundlers can become slow from config issues.
Common issues
- Source maps too expensive for local dev
- Huge transpilation scope
- Babel transforming everything
- Duplicate loaders/plugins
- Overuse of minification in dev
- No persistent cache
Improvements
- Use faster dev bundler settings
- Limit transpilation to source files only
- Prefer SWC/esbuild where possible
- Disable expensive production-only steps in dev
9) Remove expensive scripts/hooks
Sometimes the slowdown is not the build itself.
Check:
prebuild,postbuildpreparepostinstall- Git hooks
- formatting/linting on build
- codegen running every time
Fix
- Run lint/format separately or on changed files only
- Cache codegen outputs
- Avoid running tests inside build pipelines unless needed
10) Profile the build
If you still can’t find it, profile it.
Useful tools
- Nx:
nx graph,nx report, verbose logs - Turborepo:
turbo run build --profile - Webpack:
webpack --profile --json > stats.json - Next.js: build output analysis, bundle analyzer
- TypeScript:
--extendedDiagnostics - Bazel: query/profiling tools
TypeScript diagnostics
tsc --noEmit --extendedDiagnostics
Look for:
- Parse time
- Bind time
- Check time
- Emit time
11) Common monorepo anti-patterns
- One package depends on everything
- Shared utilities package is huge
- No incremental compilation
- Running full builds for tiny changes
- Dev and prod builds use same expensive pipeline
- Too many packages with small overhead and duplicated config
12) A quick triage sequence
If you want the fastest path:
- Time install, build, test, typecheck separately
- Confirm cache hits/misses
- Run affected-only builds
- Check TS project references / incremental mode
- Reduce file watching scope
- Profile the slowest step
- Inspect filesystem and antivirus issues
If you want, I can help you troubleshoot a specific stack
For example:
- Nx + TypeScript
- Turborepo + pnpm
- Next.js monorepo
- Bazel
- Vite/Webpack
If you share:
- package manager
- build tool
- repo size / package count
- what command is slow
- sample config
…I can suggest targeted fixes.