Key Takeaways
- Both npm and Yarn support lockfile-based installs and caching. The old claim that npm installs packages sequentially is no longer a useful comparison.
- Use
npm cifor npm CI pipelines,yarn install --frozen-lockfilefor Yarn Classic, andyarn install --immutablefor modern Yarn. - Modern Yarn’s Plug’n’Play changes dependency loading, but you can choose a conventional
node_moduleslayout instead. - Choose one authoritative package manager and lockfile per project. Pin the toolchain, measure installation performance, and migrate deliberately.
Neither Yarn nor npm is universally better. Both can support reproducible dependency installation; your choice should depend on project compatibility, workspace requirements, CI configuration, and team familiarity—not a speed comparison inherited from 2017.
Both tools install packages from the npm registry and share its package ecosystem. That does not make their lockfiles or configuration interchangeable.
Yarn vs npm: Compare Specific Versions
This October 17, 2026 comparison uses npm 12.2.1 and Yarn 4.18.1 as concrete reference releases. Yarn Classic means the 1.x generation; modern Yarn means the 2+ family, with the examples here targeting Yarn 4.
| Criterion | npm 12 | Yarn Classic 1.x | Modern Yarn 4 |
|---|---|---|---|
| Project lockfile | package-lock.json | yarn.lock | yarn.lock, in the modern format |
| CI install command | npm ci | yarn install --frozen-lockfile | yarn install --immutable |
| Dependency layout | node_modules | Conventional node_modules workflow | Plug’n’Play by default; node_modules available |
| Workspaces | Supported | Supported | Supported, with additional workspace tooling |
Don’t apply Classic configuration instructions to modern Yarn. Modern Yarn uses .yarnrc.yml and ignores .npmrc and Classic’s .yarnrc; the migration guide explains the configuration changes.
Reproducibility: What Lockfiles Actually Guarantee
Version ranges and resolved versions have different jobs
package.json declares your dependencies and acceptable version ranges. A lockfile records the selected resolutions, including dependencies of your dependencies. With npm’s default lockfile configuration, ordinary installation creates package-lock.json; npm ci is not required to create it. The npm lockfile documentation explicitly recommends committing this file.
Commit the lockfile belonging to your chosen package manager alongside relevant manifest changes. Don’t delete it as a routine troubleshooting step: you’re asking the resolver to make dependency choices again.
npm install already uses package-lock.json
When its recorded versions satisfy package.json, npm install uses those locked versions. When the files conflict, it can resolve dependencies again and update the lockfile. That distinction—not “npm is non-deterministic”—is the useful rule. See npm’s documented lockfile behavior and our explanation of how npm install works.
npm ci requires an existing package-lock.json, fails when it disagrees with the manifest, removes an existing node_modules, and does not rewrite either file. Keep dependency-tree-affecting configuration consistent between lockfile generation and CI.
Yarn immutable installs are not the same as clean installs
Classic’s --frozen-lockfile prevents lockfile generation and fails when an update is needed. Modern Yarn’s --immutable rejects an install that would change the lockfile; this protection is enabled by default on CI, but spelling it out makes pipeline intent clear.
These Yarn flags protect resolution state. They do not promise npm ci’s remove-and-reinstall behavior. Use a fresh checkout when you need to validate installation without existing artifacts.
Compatibility: Plug’n’Play vs node_modules
Yarn Plug’n’Play, or PnP, replaces the usual dependency directory with a loader, .pnp.cjs, that maps dependencies to their locations. It also detects access to undeclared dependencies—packages your code imports without declaring them.
That stricter model can expose existing project mistakes. It can also require editor integration or adjustments to tools and scripts that assume packages live under node_modules. Test your development server, bundler, test runner, editor, and deployment packaging before adopting it.
You don’t have to adopt PnP to use modern Yarn. To retain the conventional layout, put this setting in .yarnrc.yml, as documented in the Yarn configuration reference:
# .yarnrc.yml
nodeLinker: node-modulesBoth npm workspaces and Yarn workspaces support managing multiple packages in one repository. Modern Yarn also offers focused installs and dependency constraints. Evaluate the workspace operations you actually need alongside your JavaScript build tools.
Installation Performance and Caching
Parallelism does not establish a winner
Modern npm and Yarn both support concurrent network work; npm exposes connection controls in its configuration documentation, and Yarn exposes network concurrency settings. The original sequential-npm-versus-parallel-Yarn explanation is obsolete.
For a useful project benchmark:
- Record exact Node.js and package-manager versions, hardware, operating system, registry, and install commands.
- Measure cold-cache and warm-cache installs separately, using fresh checkouts.
- Keep dependency versions and permitted lifecycle scripts equivalent. Disclose unavoidable resolution differences.
- Measure complete install duration, including cache restoration, downloads, extraction or linking, and package builds. Repeat runs and report the spread.
Compare PnP separately from conventional installs: it changes the work required, not just the program doing it. This article does not claim a performance winner without project-specific measurements.
Both tools cache packages
npm has a content-addressable cache with integrity verification. Yarn Classic uses a global cache. Yarn 4 shares its cache across local projects by default; setting enableGlobalCache: false enables a project-local cache, normally .yarn/cache, described in Yarn’s cache strategies.
In CI, restore the configured cache location rather than assuming a developer’s global cache exists. Key caches by package-manager version and lockfile; include platform details when caching platform-dependent build artifacts. Measure cache transfer time too.
A cache does not automatically make installation offline. Required packages, metadata, the package-manager binary, and any external downloads performed by permitted scripts must be available. Test the workflow with network access disabled.
Modern Yarn’s --immutable-cache rejects cache changes. Don’t add it to a pipeline that must populate an empty cache; it serves a different purpose from --immutable.
CI Examples with Pinned Package Managers
npm: Install from the committed lockfile
Run this POSIX shell step at an existing npm project’s root on a runner with a pinned, supported Node.js patch release and a writable npm installation. npm 12.2.1’s runtime requirements include Node.js 24.15.0 or newer within the 24.x line; use an appropriately patched release, not the minimum simply because it works.
set -eu
npm install --global npm@12.2.1
npm ciAlternatively, provision that exact npm release in the runner image. Pin the runtime separately; our guide to choosing a Node.js version covers that decision.
Modern Yarn: Pin the version and reject lockfile drift
Merge this fragment into the existing package.json:
{
"packageManager": "yarn@4.18.1"
}With a compatible Corepack release already provisioned in the runner, run:
set -eu
corepack yarn install --immutableCorepack uses the project’s packageManager field to select the tool version. Don’t assume it ships with every Node.js installation: it is no longer distributed with Node.js starting at version 25. Provision and pin Corepack explicitly where needed.
Security: Locking Versions Is Not a Safety Guarantee
A version range describes acceptable updates; it does not establish that a package is malicious or vulnerable. A lockfile makes dependency changes reviewable, but it can pin an unsafe dependency just as effectively as a safe one.
Review manifest and lockfile changes together, investigate unexpected sources or package additions, and keep updates moving through tests. Use npm audit or modern Yarn’s yarn npm audit --all --recursive; the Yarn flags extend checking to all workspaces and transitive dependencies. These tools report known advisories, not proof that dependencies are safe.
Migrating from npm to Yarn Without Breaking CI
Treat migration as a dependency and infrastructure change, not a command substitution:
- Establish a baseline. Start from a clean branch and passing npm installation, tests, and build.
- Choose the target. Pin the Yarn release and decide between PnP and
node_modules. - Translate configuration. Review registry authentication, workspace behavior, lifecycle scripts, and CI cache paths. Supply credentials through CI secrets, not committed tokens.
- Generate and inspect the Yarn lockfile. Use an import workflow only when it supports your source format. Classic’s documented
yarn importis not a generic modern-Yarn migration command, and its documentation warns that not every npm dependency tree can be represented unchanged. - Validate a fresh checkout. Run the target generation’s frozen or immutable install, then tests, builds, and deployment packaging. Review resolved-version changes.
- Switch the source of truth. Remove the competing project lockfile, update CI and contributor instructions, and commit the Yarn configuration, manifest changes, and
yarn.locktogether.
For an existing project, keep the working package manager unless you can identify a concrete benefit. For a new project, npm is a sensible choice when you want a conventional workflow; modern Yarn earns consideration when its workspace tooling, PnP checks, or cache strategy solve an actual requirement. Neither requires a claim of universal superiority.
FAQ
Is Yarn still better than npm?
Not universally. Both support lockfile-based installation and caching; modern Yarn’s distinguishing choices include Plug’n’Play and workspace tooling. Choose based on compatibility, maintenance cost, and measured performance in your project.
Does npm install use package-lock.json?
Yes. When the locked versions satisfy package.json, npm install uses them; when they conflict, it can resolve dependencies again and update the lockfile. Use npm ci when CI should reject that mismatch instead.
What is the difference between npm ci and a Yarn clean install?
npm ci removes an existing node_modules directory and installs without rewriting the manifest or lockfile. Yarn Classic’s --frozen-lockfile and modern Yarn’s --immutable protect the lockfile but do not provide the same automatic cleanup behavior. Use a fresh checkout to validate a clean Yarn installation.
Can a project safely commit both package-lock.json and yarn.lock?
Do not treat both as authoritative for the same project. npm and Yarn maintain different lockfile formats, and independently updating them can make local and CI dependency choices diverge. Standardize on one unless you deliberately test and maintain separate workflows.
Can I switch from npm to Yarn easily?
The shared registry helps, but migration still requires choosing a Yarn version, reviewing dependency resolutions, translating configuration, and updating CI. Validate a fresh frozen or immutable install and the complete build before removing the npm lockfile.
Further Reading
- npm install — ordinary installation and lockfile precedence.
- npm ci — clean installation requirements and configuration consistency.
- Modern Yarn install — immutable installs and cache validation.
- Yarn Plug’n’Play — dependency loading and undeclared dependencies.
- Yarn cache strategies — shared caches, offline mirrors, and Zero-Installs.
- Yarn migration guide — configuration and script compatibility changes.
- npm install-script policy — reviewing and recording script approvals.
- Corepack documentation — provisioning and package-manager version selection.
