We moved bol’s internal Backstage platform from Yarn 4 to pnpm 12.3.0 because our parallel-development workflow uses separate Git worktrees and we wanted their installs to reuse pnpm’s shared package store. The migration was not a controlled Yarn-versus-pnpm performance test: the useful lessons were about cache paths, dependency-layout assumptions, and checking both clean and long-lived developer checkouts.
Why we changed package managers
In a first-person account published September 12, 2026, bol developer Bogdan Nechyporenko describes moving the company’s Backstage platform from Yarn 4 to pnpm 12.3.0. The practical motivation was parallel development in separate Git worktrees. A shared pnpm store can hold package content for reuse rather than requiring each worktree to maintain a wholly separate copy.
As an Amazon Associate I earn from qualifying purchases.
That workflow fit the team’s needs; it does not establish that pnpm is faster for every project, or that pnpm won a head-to-head benchmark. The reported results come from bol’s repository, GitLab CI jobs, and developer machines.
What changed in the repository and CI
The visible migration work included pinning pnpm 12.3.0 in package.json, committing pnpm-lock.yaml, replacing Yarn commands, and updating contributor documentation. The first CI run exposed a more consequential issue than the package-manager command itself: the configured store location and the path GitLab archived did not match.
#1 Best Overall
Find the store path the job actually uses
The image configured /builds/.pnpm-store, while the GitLab cache archived the project-local .pnpm-store. As a result, the cache missed and a cold run downloaded all 4,226 packages, according to Nechyporenko. Explicitly setting the intended path and printing the effective path from within the job resolved the mismatch. As he put it: “That was the first real lesson of this migration: never reason about a package-manager cache from configuration files alone. Ask the running job where its store actually is.”
This is a bol-specific path mismatch, not a universal pnpm default. For your own pipeline, inspect the effective store path in the actual runner and confirm that the cache key and archived path point to it.
Cache the store, not every dependency tree
After correcting the path, the team initially cached both the pnpm store and every node_modules tree. Four jobs restored a large archive, but dependency layouts and native modules still had to be rebuilt. Nechyporenko reports that excluding root and workspace node_modules paths reduced the archive from 1.42 GB and about 601,000 files to roughly 400 MB and 244,000 files. He estimated the smaller archive saved about six minutes per pipeline.
Recommended Free Tools
Those are approximate observations from bol’s CI, not independently audited measurements or a general speed guarantee. pnpm’s CI guidance says, “However, this is not required, and it is not guaranteed that caching the store will make installation faster.” See pnpm’s Continuous Integration documentation. Compare cache restore and save time with install, extraction, and native-build time in your own runner; retaining a cache that costs more to transfer than it saves can be counterproductive.
Rank #3
Why the old worktree helper broke
The team’s custom worktree bootstrap assumed a Yarn-oriented dependency layout. Under pnpm, the official layout uses a virtual store at node_modules/.pnpm, with package files hard-linked from the content-addressable store and symlinks forming the dependency graph. The layout is described in pnpm’s explanation of symlinked node_modules.
When the old helper’s filesystem assumptions met that structure, it hit ENOTDIR. Nechyporenko says the team replaced a roughly 370-line helper with pnpm install --frozen-lockfile. A lighter verifier checks that node_modules/.pnpm exists, but that check alone does not prove the running Backstage app is loading plugin code from a secondary worktree. The author wanted an integration check to verify that runtime behavior.
Test old checkouts as well as fresh clones
A clean checkout worked, but existing developer checkouts carried stale project-local store state from the previous configuration. Nechyporenko reports that repeated starts could relink roughly 4,800 packages and take about four minutes. The team’s one-time local startup migration removes the stale local store, root node_modules, and dependency hash, then performs a clean install.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Keep that cleanup scoped to local startup: bol intentionally retained a project-local store in CI. A migration test should cover both a fresh clone and a pre-existing checkout, including an unchanged restart and a run after dependencies change. Those cases expose stale state that a clean-clone-only test misses.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Handle slow registry connections deliberately
For slower office or VPN links, the team used a small registry request as a latency probe. After a successful request took more than three seconds, its implementation used --network-concurrency=1, five fetch retries, and a longer maximum retry timeout. This was a local response to their network conditions, not a general recommendation for pnpm projects.
The author notes an important branch in that logic: a failed probe bypasses the slow-but-successful path. That means timeout and authentication failures need deliberate handling of their own; a latency probe should not silently turn a connectivity problem into an unrelated install failure.
Separate package-manager effects from wider CI changes
The same account describes changes beyond the migration: switching Jest coverage to V8, enabling inline source maps for @swc/jest, incremental TypeScript compilation, native build prerequisites, and a keytar build policy. The author reports the Jest cache fell from 5.2 GB to no more than 2 GB, and unit-test time went from 17 minutes to about 10. These outcomes were associated with several CI changes, including test selection, coverage policy, worker limits, and cache changes; they cannot be attributed to pnpm alone. Jest’s configuration options are documented at the Jest configuration reference.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsA practical migration checklist
- Inventory dependency-layout assumptions in CI images, cache configuration, startup scripts, worktree helpers, contributor docs, and generators.
- Pin the package-manager version and commit the lockfile; replace old package-manager commands consistently.
- Print the effective store path from inside the CI job, then verify that the archived cache path matches it.
- Measure cache restore and save, install, and native-build phases separately before deciding whether to cache dependency trees.
- Test fresh clones and existing checkouts, including restarts without dependency changes and installs after changes.
- Check the virtual store structure where useful, but add an integration test if the requirement is to prove that a secondary worktree’s plugin code is what the running application loads.
- Keep network fallback behavior explicit, including what happens when the registry probe fails rather than merely runs slowly.
For bol, the migration’s value was aligning package installation with its worktree-based workflow and simplifying a helper that depended on the old layout. The operational lesson is to validate the paths and runtime behavior your own pipeline and developers actually use—not to assume the same package manager or cache strategy will yield the same result elsewhere.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




