Selecting a pnpm node-linker Mode for Workspace Monorepos
Learn how to choose between isolated, hoisted, and loose node-linker modes in pnpm workspaces to balance strict dependency isolation with tool compatibility.
06 Jun 2026, 22:44 UTC

When managing a JavaScript/TypeScript monorepo with pnpm workspaces, you often face a conflict between strict dependency isolation and tool compatibility. While pnpm's default behavior prevents "phantom dependencies" (using a package not explicitly listed in package.json), many legacy tools and plugins expect a flat node_modules structure to resolve binaries or peer dependencies.
Decision Constraints
This decision applies to projects using pnpm v8+ with a pnpm-workspace.yaml file. The goal is to maintain reproducible installs via pnpm-lock.yaml and high CI speed while ensuring that tools like ESLint, Jest, or Prettier can find their required plugins without manual path hacking.
The node-linker setting in the root .npmrc determines the physical layout of the node_modules directory. Changing this setting does not alter the lockfile's dependency graph, but it changes how the Node.js resolution algorithm finds those files on disk.
Comparing node-linker Options
| Mode | node-linker value | Hoisting | Isolation | Typical Use Case |
|---|---|---|---|---|
| Isolated | isolated |
None | Strict (Symlinks) | Default; safest for modern apps |
| Hoisted | hoisted |
Full Flatten | Low | Legacy tools requiring flat trees |
| Loose | loose |
Selective | Medium | Specific legacy compatibility |
Note: The shamefully-hoist setting is deprecated. Use node-linker and public-hoist-pattern instead; mixing them can lead to unpredictable resolution behavior.
Trade-offs and Resolution Behavior
Isolated Mode
In isolated mode, pnpm creates a nested .pnpm directory containing all dependencies, then uses symlinks to expose only the dependencies explicitly declared in package.json. This ensures deterministic builds and prevents a package from accidentally importing a dependency it doesn't own.
The trade-off is that some tools walk the file system upwards to find plugins. If a plugin is a dependency of a dependency, the tool will fail to find it because it isn't hoisted to the root.
Hoisted Mode
The hoisted mode mimics the npm or yarn layout by flattening dependencies into the root node_modules. This maximizes compatibility with older tools and scripts that expect a flat structure.
The trade-off is the loss of isolation. You risk "phantom dependencies," where code works locally because a package happened to be hoisted, but fails in CI or production when the dependency tree shifts.
Loose Mode
loose provides a middle ground with partial hoisting. However, its behavior is highly sensitive to pnpm versions and is generally harder to reason about in strict CI environments compared to the other two modes.
Implementation Guide
First, define your workspace boundaries in pnpm-workspace.yaml at the repository root:
packages:
- 'apps/*'
- 'packages/*'
Next, configure the linker in the root .npmrc. For most projects, the best balance is Isolated mode with selective hoisting for specific tools:
# .npmrc
node-linker = isolated
public-hoist-pattern = ['*eslint*', '*prettier*']
If your toolchain is entirely incompatible with symlinks, use full hoisting:
# .npmrc
node-linker = hoisted
Execution: Run the following command from the root terminal (ensure you have write permissions to the directory):
pnpm install
Risk: Switching node-linker modes leaves stale artifacts. You must delete all node_modules folders recursively before running pnpm install after a mode change to avoid mixed-state resolution errors.
Validation and Verification
To verify the active configuration, run these commands in your terminal:
pnpm --version
pnpm config list
To validate the physical layout, run an install with the summary reporter:
pnpm install --reporter summary
- In Isolated Mode: Check a workspace
node_modulesfolder. You should see symlinks pointing to a hidden.pnpmdirectory. - In Hoisted Mode: Check the root
node_modules. You should see a flat list of packages without the.pnpmsymlink structure.
Finally, execute your workspace test scripts. If you switched from hoisted to isolated and tests fail with "Module not found," you likely have a phantom dependency that needs to be explicitly added to the relevant package.json or added to the public-hoist-pattern.
Limitations
The public-hoist-pattern only functions when node-linker is set to isolated. Furthermore, because hoisting behavior can vary between pnpm major releases, always verify your node_modules layout after updating the pnpm CLI.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.