Best npm Workspaces Alternatives 2026
TL;DR
npm workspaces are the least capable option — pnpm workspaces are the 2026 default for monorepos. npm workspaces lack per-workspace filtering, have no dependency isolation (all packages see all deps via hoisting), and have slower install. pnpm workspaces + Turborepo or Moon is the dominant stack: pnpm handles package resolution and isolation, Turborepo handles task caching. Yarn Berry (PnP mode) is a third option for teams already on Yarn.
Key Takeaways
- pnpm workspaces: Best filtering (
--filter), strictest isolation,pnpm-workspace.yaml - Bun workspaces: Fastest install, basic filtering, JSON-only config
- npm workspaces: Universal but limited filtering, no isolation, slowest
- Yarn Berry (PnP): Zero node_modules, but PnP compatibility issues persist in 2026
- Combine with: Turborepo, Nx, or Moon for task caching and orchestration
- Protocol:
workspace:*in pnpm and Yarn links local packages cleanly
Why npm Workspaces Fall Short
npm introduced workspaces in npm 7, and the feature works — up to a point. For a monorepo with two or three packages, npm workspaces are perfectly adequate. For anything larger, the limitations become painful.
The core problem is hoisting. npm workspaces install all dependencies into the root node_modules directory. This means a package in packages/ui can accidentally import a dependency declared by packages/api without any error. These phantom dependencies work locally but can fail after reorganizing your repo or on a clean CI install where hoisting behaves differently. The fundamental issue is that npm workspaces do not enforce the boundary between what a package declares and what it can access.
The second limitation is filtering. pnpm's --filter can run a command in a specific package plus all its dependencies (pnpm --filter web...), or in all packages that depend on a given package (pnpm --filter ...@myapp/ui). npm offers no equivalent — you can run a command in a specific workspace by name, but graph-based traversal is absent.
The third limitation is speed. npm installs are roughly three times slower than pnpm installs on a comparable monorepo, because pnpm uses a content-addressable store where packages are downloaded once and hard-linked rather than copied.
Package Health Table
| Tool | Weekly Downloads | Isolation | Graph Filter | Install Speed | workspace: protocol |
|---|---|---|---|---|---|
| npm workspaces | ~25M (npm) | No (hoisted) | No | Slow | No |
| pnpm workspaces | ~10M | Yes (strict) | Yes | Fast | Yes |
| Bun workspaces | ~3M | Partial | Basic | Fastest | No (experimental) |
| Yarn Berry | ~3M | Yes (PnP) | Yes | Fast | Yes |
pnpm Workspaces: The Standard
pnpm is the dominant monorepo package manager in 2026. Its content-addressable store means each version of every package is stored once on disk and hard-linked into each project that uses it — reducing both install time and disk usage compared to npm. Strict isolation means each package can only access the dependencies it declares, eliminating phantom dependency bugs.
The pnpm-workspace.yaml file at the repo root declares which directories contain packages:
# pnpm-workspace.yaml (root):
packages:
- 'apps/*'
- 'packages/*'
- 'tools/*'
- '!**/node_modules/**'
Local packages reference each other using the workspace:* protocol. When you publish, pnpm replaces workspace:* with the actual version number automatically.
// packages/ui/package.json — local dependency:
{
"name": "@myapp/ui",
"version": "1.0.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
}
// apps/web/package.json — consuming the local package:
{
"name": "web",
"dependencies": {
"@myapp/ui": "workspace:*",
"react": "^19.0.0"
}
}
pnpm Filtering
The --filter flag is pnpm's most powerful monorepo feature. It lets you scope commands to specific packages, their dependencies, or packages that depend on them:
# Run command in specific package:
pnpm --filter web run dev
pnpm --filter @myapp/ui run build
# Run in package and all its dependencies (build deps first):
pnpm --filter web... run build
# Run in packages that depend on @myapp/ui:
pnpm --filter ...@myapp/ui run build
# Run in all packages matching glob:
pnpm --filter './packages/**' run test
# Add dep to specific workspace:
pnpm --filter web add axios
pnpm --filter @myapp/ui add -D vitest
# Run in all packages:
pnpm -r run build # Recursive
pnpm -r run test --if-present # Only if test script exists
pnpm Workspace Isolation
The isolation model is a key differentiator:
pnpm workspace isolation (default):
packages can only import what they explicitly declare
symlinks to packages/@myapp/ui (not copied)
phantom dependencies cause errors (not silently allowed)
each package node_modules contains only what it declares
npm workspace (hoisted, no isolation):
all packages share root node_modules
apps/web can accidentally import packages/ui's deps
phantom dependencies silently work until they don't
this is why "works on my machine" happens in npm monorepos
npm Workspaces: Universal Baseline
npm workspaces require no additional tooling — they are built into npm since version 7. If your monorepo has two or three packages and you are already on npm, workspaces work without adding a new package manager.
// package.json (root):
{
"name": "my-monorepo",
"workspaces": [
"apps/*",
"packages/*"
]
}
# npm workspace commands:
npm install # Install all workspaces
npm install --workspace=apps/web # Install specific workspace
npm run build --workspace=apps/web # Run in specific workspace
npm run build --workspaces # Run in all workspaces
npm run build --workspaces --if-present # Skip if no script
The gaps relative to pnpm become apparent as the monorepo grows:
- No
--filterwith dependency graph traversal - No "build web and all its deps" command
- No strict isolation (phantom dependencies silently allowed)
- Install is roughly three times slower than pnpm
- No
workspace:*protocol (uses relative paths instead) - No way to enforce that
apps/webcannot accesspackages/api's dependencies
npm workspaces are a reasonable starting point for small repos. The upgrade path to pnpm is straightforward — delete node_modules, create pnpm-workspace.yaml, and change the package references from relative paths to workspace:*. Most teams make this move when the npm limitations start causing real problems at around five or more packages.
Bun Workspaces: Speed-First
Bun workspaces use the same workspaces field in package.json as npm — there is no separate config file:
// package.json (root) — same format as npm:
{
"name": "my-monorepo",
"workspaces": [
"apps/*",
"packages/*"
]
}
# Bun workspace commands:
bun install # Install all (fast!)
bun add react --workspace apps/web # Add to specific workspace
bun run --filter '*' build # Run in all workspaces
bun run --filter 'apps/*' dev # Run in apps only
Bun's install is the fastest of any package manager — typically 5–10x faster than npm and 2–3x faster than pnpm on cold installs. For monorepos with many packages and frequent clean installs (common in CI), this speed difference is meaningful.
The trade-offs: Bun's --filter is glob-based only, with no dependency graph traversal. The workspace:* protocol is experimental and has compatibility issues with some publishing workflows. Some packages with native addons or unusual install scripts do not install correctly with Bun. In 2026, Bun workspaces are best suited for smaller monorepos where install speed is the top priority and the compatibility edge cases have been validated against your specific dependencies.
Yarn Berry (PnP): Zero node_modules
Yarn Berry (Yarn 2+) introduced Plug'n'Play (PnP) as an alternative to the node_modules directory. In PnP mode, packages are stored in a zip cache and resolution happens via a generated .pnp.cjs file rather than directory traversal. There is no node_modules folder at all.
# Enable Yarn Berry in existing project:
yarn set version stable
# .yarnrc.yml:
nodeLinker: pnp # Plug'n'Play (no node_modules)
# Or:
nodeLinker: node-modules # Traditional (more compatible)
# Yarn workspaces commands:
yarn workspace web add axios # Add to specific workspace
yarn workspaces foreach run build # Run in all workspaces
yarn workspaces foreach -p run build # Parallel across workspaces
yarn workspaces foreach -A --topological run build # Topological order
Yarn Berry workspaces support graph-aware filtering similar to pnpm, and the workspace:* protocol works the same way. The strict isolation in PnP mode is even stricter than pnpm — packages cannot access anything not declared in their own package.json.
The challenge in 2026 is compatibility. PnP requires every tool that resolves modules to understand the PnP resolution algorithm. Most major tools now have PnP support (Webpack, Rollup, Vitest, Next.js), but you may encounter edge cases with less common packages or CLI tools. The Yarn SDK configuration for editors (VS Code, JetBrains) adds setup overhead.
Yarn Berry's node-modules linker mode (set in .yarnrc.yml) is fully compatible with all tools but loses the PnP advantages. Teams that want Yarn's foreach syntax and workspace:* without PnP compatibility risk can use this mode.
Turborepo + pnpm: The Dominant Stack
The package manager handles installation and dependency isolation. The task runner handles build orchestration and caching. In 2026, the dominant combination is pnpm for packages and Turborepo for tasks.
Turborepo's key feature is caching: it hashes the inputs to each task (source files, env vars, dependencies) and skips the task if the output cache is still valid. The first turbo run build builds everything. The second run with no changes completes in milliseconds because all tasks hit the cache.
# Install Turborepo:
npm install --save-dev turbo
# Or globally:
npm install -g turbo
// turbo.json — task dependency graph:
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"test": {
"dependsOn": ["^build"],
"outputs": []
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
}
}
}
The ^build syntax means "run build in all dependencies of this package first". This handles the common monorepo problem of running tasks in the correct topological order automatically.
# Turborepo commands:
turbo run build # Build all packages (cached)
turbo run build --filter=web # Build only web (and its deps)
turbo run build --filter=...@myapp/ui # Build all that depend on @myapp/ui
turbo run dev # Run dev servers (not cached)
turbo run build --dry # Show what would run without running
Vercel provides a remote cache for Turborepo that can be shared across team members and CI runs. With remote cache, a CI run with no changes to packages/ui will skip the packages/ui build and download the cached output instead — even on a fresh CI instance.
The recommended full stack for most JavaScript monorepos in 2026:
Package manager: pnpm workspaces
Task runner: Turborepo v2 (Rust-based scheduler)
CI caching: Vercel Remote Cache (Turborepo) or GitHub Actions cache
Setup steps:
1. pnpm-workspace.yaml with apps/* and packages/* globs
2. turbo.json with build/test/lint/dev tasks
3. workspace:* protocol in all internal package deps
4. pnpm --filter for workspace-specific commands
Comparison Table
| Feature | pnpm | Bun | npm | Yarn Berry |
|---|---|---|---|---|
| Install speed | Fast | Fastest | Slow | Fast |
| Disk usage | Low | Low | High | Lowest (PnP) |
| Isolation | Strict | Partial | None | Strict |
| Graph filtering | Yes (--filter) | Partial | No | Yes |
| workspace: protocol | Yes | No (experimental) | No | Yes |
| Compatibility | High | Medium | Highest | Medium |
| Turborepo support | Yes | Yes | Yes | Yes |
| Nx support | Yes | Yes | Yes | Yes |
When to Choose
pnpm workspaces is the right default for most JavaScript monorepos in 2026. Strict isolation eliminates phantom dependency bugs. The --filter graph traversal simplifies running commands in dependency order. Content-addressable storage means fast installs and low disk usage. If you are starting a new monorepo, start with pnpm.
npm workspaces make sense for small repos (two to three packages) where the limitations have not yet become painful, or for teams that need maximum tooling compatibility without installing a new package manager. The upgrade path to pnpm is easy when you outgrow it.
Bun workspaces are worth considering for small monorepos where install speed is the priority and you have validated that your specific dependencies are all Bun-compatible. The compatibility surface area is still smaller than pnpm.
Yarn Berry is the right choice for teams already invested in the Yarn ecosystem who want the workspace:* protocol and foreach syntax. PnP mode is technically impressive but requires editor and tool configuration that adds setup overhead.
Related Reading
- How to choose between npm, pnpm, and Yarn: /blog/how-to-choose-npm-pnpm-yarn-2026
- pnpm package health and download trends: /packages/pnpm
- How to reduce node_modules size in large projects: /blog/how-to-reduce-node-modules-size