Articles

npm vs pnpm vs Yarn: Which Package Manager to Use

npm, pnpm, and Yarn all install the same packages but differ in speed, disk usage, and monorepo support. Here's how to pick the right one.

Takina Takina · · 4 min read
A code editor showing a component file

npm, pnpm, and Yarn are the three package managers most JavaScript projects choose between, and the biggest practical differences come down to how each one stores dependencies on disk and how fast it resolves them. They all read the same package.json and produce a working node_modules, but the mechanics underneath diverge enough to matter on a large project.

npm: the default that ships with Node

npm is installed automatically with Node.js, which makes it the path of least resistance — no extra install step, no team-wide decision required. Since npm 7, it supports workspaces natively, so basic monorepos work without a separate tool.

npm’s dependency resolution flattens node_modules as much as possible: packages get hoisted to the top level so nested duplicates are avoided where semver allows it. This keeps most installs reasonably compact, but it also means the tree’s shape can shift subtly between installs unless the lockfile (package-lock.json) is committed and respected.

Yarn: two different tools under one name

“Yarn” now refers to two distinct generations. Yarn Classic (v1) behaves similarly to npm — a flat, hoisted node_modules — but was historically faster and had more deterministic lockfiles when it launched. Yarn Berry (v2+) is a bigger departure: it defaults to Plug’n’Play (PnP), which skips node_modules entirely and resolves imports through a generated map instead. PnP installs are fast and avoid the “phantom dependency” problem — code accidentally importing a package it never declared — but it can break tools that assume a real node_modules directory exists on disk.

Most teams that still use Yarn today are running Classic, or Berry configured back into node-modules linker mode for compatibility.

pnpm: content-addressable storage

pnpm takes a different approach to the disk-usage problem. Instead of copying every package into every project’s node_modules, it stores one copy of each package version in a global content-addressable store and uses hard links (or symlinks) to wire it into each project. Across ten projects that all depend on the same version of a library, pnpm keeps that library on disk exactly once.

pnpm also does not flatten the dependency tree by default — a package only gets a symlink to dependencies it actually declares in package.json. This makes it strict about phantom dependencies without going as far as PnP’s non-standard resolution, which is part of why pnpm has become the common choice for monorepos that need both speed and correctness.

Comparison

npmYarn ClassicYarn Berry (PnP)pnpm
Ships with NodeYesNoNoNo
node_modules structureFlat, hoistedFlat, hoistedNone (PnP map)Symlinked, strict
Disk usage across projectsDuplicated per projectDuplicated per projectMinimal (no node_modules)Minimal (shared store)
Phantom dependency protectionNoNoYes (strict resolution)Yes (strict by default)
Monorepo workspacesBuilt in (v7+)Built inBuilt inBuilt in, widely used for this
Tooling compatibilityUniversalUniversalSome tools break without node_modulesUniversal (real node_modules, just linked)

Lockfiles and reproducible installs

All three generate a lockfile that pins exact resolved versions: package-lock.json, yarn.lock, and pnpm-lock.yaml respectively. Commit whichever one your tool produces, and run installs in CI with the “frozen” flag (npm ci, yarn install --immutable, or pnpm install --frozen-lockfile) so a stray dependency bump doesn’t silently change what ships. This matters as much for a small app as it does for a CI/CD pipeline running hundreds of times a day — reproducibility is the whole point of a lockfile.

Which one should you use

  • npm is the safe default for a single-package project where you don’t want to introduce another tool. It’s improved a lot on speed since its early versions and needs no setup.
  • pnpm is worth adopting deliberately for monorepos or any team that’s hit real disk-space or install-time pain — the shared store and strict resolution solve concrete problems without changing how imports work.
  • Yarn Classic is a reasonable choice if a codebase already uses it; there’s rarely a reason to migrate away just for its own sake.
  • Yarn Berry with PnP is the riskiest pick for compatibility — check that your build tools, editor, and deploy target (including something like Cloudflare Pages) all support it before committing to it.

None of these change what JavaScript actually executes at runtime — that’s governed by module resolution and bundlers like Vite, not the package manager. The choice is purely about install speed, disk footprint, and how strictly the tool enforces that your dependencies are what you declared.

The takeaway

npm, Yarn, and pnpm all solve the same problem — resolving package.json into a working dependency tree — with different tradeoffs. npm is the zero-setup default, Yarn Classic behaves similarly with its own lockfile format, Yarn Berry’s PnP mode trades node_modules for a faster but less compatible resolution model, and pnpm’s shared store gives you both speed and strict dependency correctness at the cost of a symlinked node_modules. For most new projects, especially monorepos, pnpm is the strongest default; for everything else, whichever tool your team already knows is fine.

Takina Takina · · 4 min read

What Is a Lockfile? Reproducible Dependency Installs

A lockfile records the exact dependency versions your package manager resolved, so every install — from your laptop to CI — reproduces the same tree.

#JavaScript #Web Development #Developer Tools
Takina Takina · · 4 min read

JavaScript Intl API: Formatting Dates and Numbers

The Intl API formats dates, numbers, and currency using a user's locale without a library. How Intl.DateTimeFormat and Intl.NumberFormat work.

#JavaScript #Web Development #Developer Tools
Takina Takina · · 5 min read

Turbopack vs Webpack: Choosing a JS Bundler

Turbopack is a Rust-based bundler built for incremental speed; Webpack is the mature, plugin-heavy standard. How they differ and when to pick each.

#JavaScript #Web Development #Developer Tools