Yarn exists in two eras. Yarn Classic (v1) is a parallel installer that writes a familiar hoisted node_modules tree and a text yarn.lock. Yarn Berry(v2+) is a rewrite: project-local binaries, Plug'n'Play (PnP), optional Zero-Installs, a plugin system, and stronger workspace tooling.
New projects should target Berry (stable). Classic remains only for legacy repos that cannot migrate yet. Compare managers on npm vs Yarn vs pnpm vs Bun.
📝
note
Prefer installing Yarn via Corepack and the packageManager field — not a global npm i -g yarn (that often still installs Classic).
Classic vs Berry
Aspect
Yarn Classic (v1)
Yarn Berry (v2+)
Install layout
Hoisted node_modules
PnP by default; node-modules optional
Binary
Global yarn
.yarn/releases/*.cjs per project
Config
.yarnrc
.yarnrc.yml
Plugins
Limited
First-class plugin API
Zero-Installs
No
Yes
Maintenance
Legacy
Active
enable-berry.sh
Bash
1
corepack enable
2
# from project root
3
yarn set version stable
4
# writes .yarn/releases/yarn-*.cjs and updates packageManager
5
yarn --version
How / Why / Requirements
Question
Answer
Why choose Yarn?
Deterministic installs, workspaces, PnP/Zero-Installs, constraints for monorepo policy
How to adopt?
Corepack → yarn set version stable → yarn install → commit yarn.lock + .yarn/releases
Requirements
Node with Corepack (or committed Yarn binary); CI must use same Yarn version; PnP needs editor SDKs
When not to?
Team prefers pnpm's store model; many packages break under PnP and you refuse nodeLinker fallback
yarn.lock
yarn.lock pins exact resolved versions and integrity hashes for every transitive dependency. Always commit it. Never hand-edit unless you know the format — regenerate with yarn install.
lockfile-ops.sh
Bash
1
yarn install # update tree + lockfile as needed
2
yarn install --immutable # CI: fail if lockfile would change (Berry)
3
yarn up lodash # interactive/range-aware upgrade (Berry)
4
yarn dedupe # reduce duplicate versions when possible
⚠
warning
Classic and Berry share the filename yarn.lock but the lockfile format differs. Migrating Classic → Berry rewrites the lockfile; review the diff in PRs.
Plug'n'Play (PnP)
PnP replaces nested node_modules with a generated map (.pnp.cjs / .pnp.loader.mjs). Node resolves packages through Yarn's runtime instead of walking directories. Packages you did not declare cannot be required — phantom dependencies fail loudly.
.yarnrc.yml
YAML
1
# Default Berry behavior is PnP (nodeLinker: pnp)
2
# Explicit:
3
nodeLinker: pnp
4
5
# Compatibility escape hatch:
6
# nodeLinker: node-modules
7
# nodeLinker: pnpm # hybrid layout option in newer Yarn
editor-sdks.sh
Bash
1
# Generate IDE / TypeScript SDKs for PnP
2
yarn dlx @yarnpkg/sdks vscode
3
# commit .yarn/sdks as recommended by Yarn docs for your editor
Benefit
Trade-off
Faster installs, less disk churn
Some packages assume node_modules paths
Strict dependency graph
Need packageExtensions for broken packages
Enables Zero-Installs cleanly
Editor/tooling setup required
ℹ
info
If a dependency breaks under PnP, prefer packageExtensions in .yarnrc.yml over flipping the whole repo to node-modules — fix the graph, do not abandon strictness unless necessary.
Zero-Installs
Zero-Installs commit the Yarn cache (zip archives under .yarn/cache) plus PnP maps so clones can run without a network install. CI skips yarn install or reduces it to a no-op validation.
.yarnrc.yml
YAML
1
enableGlobalCache: false
2
# keep cache inside the project so it can be committed
# often: yarn immediately works; or yarn install --immutable as a check
9
yarn test
⚠
warning
Repo size grows with dependency zips (often 100MB+). Use Git LFS for .yarn/cache if clone size becomes painful. Re-evaluate if most contributors use shallow CI caches instead.
Constraints
Yarn Constraints are Prolog-inspired (or JS) rules that enforce workspace policies: same React version everywhere, forbidden packages, required peer ranges.
constraints.sh
Bash
1
yarn plugin import constraints # if not already present
2
yarn constraints # report violations
3
yarn constraints --fix # auto-fix when possible
yarn.config.cjs (conceptual)
JavaScript
1
// Berry constraints configuration varies by Yarn major —
2
// define rules that all workspaces use the same typescript major
3
// and disallow left-pad-style banned packages.
4
module.exports = {
5
// See Yarn docs for your version's constraints API shape
6
};
✓
best practice
Run yarn constraints in CI after install. Treat violations like lint failures — they prevent version drift across a monorepo.
Workspaces
Workspaces live under one root lockfile. Internal packages link by name without publishing.
package.json
JSON
1
{
2
"name": "acme",
3
"private": true,
4
"workspaces": ["apps/*", "packages/*"],
5
"packageManager": "yarn@4.5.0"
6
}
workspace-cmds.sh
Bash
1
yarn workspaces list
2
yarn workspace @acme/web add zod
3
yarn workspaces foreach -A --topological run build
4
yarn workspaces focus @acme/web # install subset for deploy images
Command pattern
Use when
yarn workspace name …
Operate on one package
yarn workspaces foreach …
Run script across many packages
workspaces focus
Production image with only needed deps
yarn dlx
yarn dlx is the Berry equivalent of npx: download a package to a temporary environment and execute its binary without adding it to your project.
dlx.sh
Bash
1
yarn dlx create-next-app@latest my-app
2
yarn dlx eslint . --fix
3
yarn dlx serve dist
🔥
pro tip
Prefer project-local binaries via yarn run for CI. Use dlx for scaffolding and one-off tools so versions stay intentional.
Plugins
Berry ships a slim core; optional features arrive as plugins stored under .yarn/plugins and referenced from .yarnrc.yml.