| |

Yarn 09 ๐Ÿงถ Zero-Installs Configuration

โฑ๏ธ Estimated read: 10 min | |

Zero-Installs is a Yarn pattern that eliminates the need to run yarn install after cloning a repository or switching branches. It works by committing the install artifactsโ€”the PnP loader files and the project-local package cacheโ€”directly to version control. The result is a repository that is immediately runnable on checkout, with no network requests and no installation step. Yarn 4 disabled Zero-Installs by default for new projects, but the pattern remains fully supported and documented for teams that choose it .

Key point: Zero-Installs requires three things committed to Git: enableGlobalCache: false in .yarnrc.yml, the .yarn/cache/ directory containing package archives, and the .pnp.cjs loader file. Without all three, the repository is not self-contained.


Why Zero-Installs exists

The branch-switching problem. In a traditional node_modules setup, switching branches often changes the lockfile. The developer must remember to run yarn install before running tests or starting the dev server. If they forget, tools crash with confusing errors. Zero-Installs makes git checkout effectively double as yarn install: the VCS updates the cache and loader files as part of the branch switch, and the project is immediately runnable .

The network reliability problem. Every yarn install fetches packages from the npm registry. When the registry has an outageโ€”or when a package is unpublishedโ€”the install fails. By committing the cache to the repository, every commit is guaranteed to be installable regardless of external service availability. The repository becomes its own package registry .

The node_modules problem. Checking in node_modules is technically possible but impractical. The directory contains tens of thousands of files, hoisting causes them to move unpredictably when dependencies change, and developers have a bad habit of editing files inside it manually. Git tracks every change as a separate diff, bloating the repository and making reviews impossible . Zero-Installs with PnP avoids this: the cache contains one zip archive per package, the dependency tree is flat, and Git can track each package as a single file .

The Yarn 4 default change. Yarn 4 no longer enables Zero-Installs by default for new projects created with yarn init . This is not because the pattern is deprecatedโ€”it remains fully supportedโ€”but because Yarn 4 introduced Lazy Installs as an alternative that scales better on very large monorepos. Zero-Installs remains a viable choice for smaller projects or teams that prioritize reproducibility over repository size .

The trade-off. Committing the cache increases repository size. The .yarn/cache directory contains one zip per dependency, and for large projects, this can add hundreds of megabytes to the repository. The .pnp.cjs file also grows with the dependency graph, and in very large monorepos, it can reach tens of megabytes on its own . The benefit is complete reproducibility and zero install time.


a. Enabling the local cache and committing artifacts

The first step is configuring Yarn to store packages in the project directory rather than the global cache. By default, Yarn Berry uses a global cache in ~/.yarn/berry/cache, which is not portable across machines. Setting enableGlobalCache: false in .yarnrc.yml redirects the cache to .yarn/cache within the project .

# .yarnrc.yml
enableGlobalCache: false
nodeLinker: pnp

After changing this setting, run yarn install to populate the local cache. The .yarn/cache/ directory will contain one .zip file for each package in the dependency tree. The .pnp.cjs file is also generated at the project root.

The .gitignore file must be updated to not ignore these artifacts. A typical Yarn .gitignore ignores .yarn/* and .pnp.*, which would exclude the exact files Zero-Installs requires . The correct configuration explicitly un-ignores the cache and loader files.

# .gitignore
.yarn/*
!.yarn/cache
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
.pnp.*
!.pnp.cjs
!.pnp.loader.mjs

The negation patterns (!) tell Git to include these paths even though the parent directory is ignored. After adding the cache and loaders, commit them alongside .yarnrc.yml and yarn.lock.


b. Verifying Zero-Installs in CI

A repository with Zero-Installs should be validated to ensure the committed artifacts are consistent with the lockfile and that no package has been tampered with. Yarn provides two flags for this: --immutable-cache and --check-cache .

# Validate that the cache matches the lockfile
yarn install --immutable --immutable-cache

# Also verify checksums of cached packages (recommended for external PRs)
yarn install --immutable --immutable-cache --check-cache

The --immutable flag prevents Yarn from modifying the lockfile. The --immutable-cache flag aborts if the cache would need to be modifiedโ€”either because a package is missing or because the lockfile references a package not present in the cache . The --check-cache flag is the security layer: it refetches packages and verifies that their checksums match what the lockfile and the committed cache contain. This is essential when accepting pull requests from third parties, as a malicious contributor could otherwise alter the committed cache files before submitting the PR .

For a CI workflow, the install step with these flags is the only verification needed. If the cache is missing a package or the checksums do not match, the build fails immediately rather than proceeding with an inconsistent state.


c. Limitations and when to avoid Zero-Installs

Zero-Installs has specific limitations that determine whether it is appropriate for a project. The most significant is native dependencies. Packages that compile native code (using node-gyp, for example) cannot be executed directly from within a zip archive. When such a package is added or updated, yarn install must be run to build the native component . Yarn displays a helpful error message if this is forgotten, but the pattern is not entirely install-free for these packages.

The second limitation is repository size. The .pnp.cjs file contains the full dependency map and can grow large in monorepos with many workspaces. In one documented case, the .pnp.cjs file alone reached 25 MiB, and Git’s delta algorithm handled it poorly . The cache itself also adds size, though the zips are uncompressed to allow Git to compute deltas between versionsโ€”counterintuitively, uncompressed zips produce a smaller repository over time than compressed ones .

The third limitation is the Yarn version upgrade path. Some Yarn releases have required regenerating the entire cache, producing a large diff in Git. Modern Yarn versions control the byte representation of cache files more carefully, reducing unnecessary updates, but the risk remains .

For very large monorepos with frequent dependency changes, Yarn’s Lazy Installs is the recommended alternative. It does not commit the cache, but uses a local cache and a smarter install strategy to reduce the cost of switching branches. Zero-Installs remains appropriate for smaller projects, internal tools, or teams that prioritize absolute reproducibility over repository size .


Complete Example Session

# ============================================
# PART 1: CONFIGURE LOCAL CACHE
# ============================================

# .yarnrc.yml
enableGlobalCache: false
nodeLinker: pnp
# ============================================
# PART 2: RUN INSTALL TO POPULATE CACHE
# ============================================

yarn install
# The local cache is populated at .yarn/cache/
# The loader is generated at .pnp.cjs
# ============================================
# PART 3: UPDATE GITIGNORE
# ============================================

# .gitignore
.yarn/*
!.yarn/cache
!.yarn/releases
.pnp.*
!.pnp.cjs
!.pnp.loader.mjs
# ============================================
# PART 4: VERIFY THE CACHE EXISTS
# ============================================

ls .yarn/cache/ | head -n 5
# Output: package-name-npm-1.0.0-abc123.zip
# ============================================
# PART 5: COMMIT THE ARTIFACTS
# ============================================

git add .yarnrc.yml .yarn/cache .pnp.cjs .pnp.loader.mjs yarn.lock
git commit -m "Enable Zero-Installs"
git push
# ============================================
# PART 6: VERIFY ON A FRESH CLONE
# ============================================

git clone <repository-url> test-clone
cd test-clone
ls node_modules 2>/dev/null || echo "No node_modules (expected)"
# The project is immediately runnable
yarn node -e "console.log('Zero-Installs works')"
# ============================================
# PART 7: CI VALIDATION
# ============================================

# In CI, validate the committed cache
yarn install --immutable --immutable-cache --check-cache

The seven parts trace the Zero-Installs setup: configuring the local cache, running install, updating .gitignore, verifying the cache, committing the artifacts, testing on a fresh clone, and validating in CI.


Quick Reference

Required Configuration

SettingLocationValue
enableGlobalCache.yarnrc.ymlfalse
nodeLinker.yarnrc.ymlpnp
Cache directory.yarn/cache/Committed to Git
Loader file.pnp.cjsCommitted to Git

CI Validation Flags

FlagPurpose
--immutableLockfile must not change
--immutable-cacheCache must not change
--check-cacheVerify package checksums

Files to Commit

File/DirectoryPurpose
.yarnrc.ymlConfiguration
yarn.lockDependency resolutions
.yarn/cache/Package archives
.pnp.cjsPnP loader
.pnp.loader.mjsESM loader

Best Practices

โœ… Do This:

enableGlobalCache: false
<!-- Local cache is required for Zero-Installs -->   # โœ…
yarn install --immutable --immutable-cache --check-cache
<!-- Validate cache in CI -->                        # โœ…
!.yarn/cache
!.pnp.cjs
<!-- Un-ignore Zero-Installs artifacts -->          # โœ…

โŒ Don’t Do This:

# Commit node_modules instead of .yarn/cache
<!-- Bloats repo, breaks on hoisting changes -->     # โŒ
# Ignore .yarn/cache in .gitignore
<!-- Zero-Installs requires the cache -->            # โŒ
# Skip --check-cache with external PRs
<!-- Allows cache tampering -->                      # โŒ

Common Pitfalls

PitfallWhy It HappensFix
Fresh clone fails to run.yarn/cache not committedUn-ignore with !.yarn/cache
Cache mismatch in CILocal cache differs from lockfileRun yarn install and commit updated cache
Native dependency failsZip cannot contain compiled binariesRun yarn install after adding native deps
Repository too largeCache and .pnp.cjs committedConsider Lazy Installs for large monorepos
Tampered cache in PRContributor modified zip filesUse --check-cache in CI

Real-World Examples

1. Enable Local Cache

enableGlobalCache: false

2. Commit Cache

git add .yarn/cache .pnp.cjs yarn.lock

3. Un-Ignore Cache

!.yarn/cache
!.pnp.cjs

4. Fresh Clone Test

git clone <url> && cd <repo> && yarn node -e "console.log('ok')"

5. CI Validation

yarn install --immutable --immutable-cache --check-cache

6. Check Cache Size

du -sh .yarn/cache

7. List Cached Packages

ls .yarn/cache | wc -l

8. Native Dependency Note

Adding a native package requires yarn install to compile.

9. Fallback to Global Cache

enableGlobalCache: true

10. Switch to Lazy Installs

Remove cache from Git; Yarn 4 defaults to Lazy Installs.

Visual

Zero-Installs Repository Contents

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Repository (after Zero-Installs commit)     โ”‚
โ”‚                                              โ”‚
โ”‚  โ”œโ”€โ”€ .yarnrc.yml                             โ”‚
โ”‚  โ”‚   โ””โ”€โ”€ enableGlobalCache: false            โ”‚
โ”‚  โ”œโ”€โ”€ yarn.lock                               โ”‚
โ”‚  โ”œโ”€โ”€ .pnp.cjs          (dependency map)      โ”‚
โ”‚  โ”œโ”€โ”€ .pnp.loader.mjs   (ESM loader)          โ”‚
โ”‚  โ””โ”€โ”€ .yarn/                                  โ”‚
โ”‚      โ””โ”€โ”€ cache/                              โ”‚
โ”‚          โ”œโ”€โ”€ lodash-npm-4.17.21-abc.zip      โ”‚
โ”‚          โ”œโ”€โ”€ express-npm-4.18.2-def.zip      โ”‚
โ”‚          โ””โ”€โ”€ ...                             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The repository contains everything needed to run without yarn install.

CI Validation Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  CI runner checks out repository             โ”‚
โ”‚  (cache and loaders are present)             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  yarn install --immutable --immutable-cache  โ”‚
โ”‚  --check-cache                               โ”‚
โ”‚                                              โ”‚
โ”‚  โ”œโ”€ Lockfile matches cache?                  โ”‚
โ”‚  โ”œโ”€ Checksums match?                         โ”‚
โ”‚  โ””โ”€ No modifications required?               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ”‚ PASS                  โ”‚ FAIL
           โ–ผ                       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Proceed with tests  โ”‚  โ”‚  Abort: cache is     โ”‚
โ”‚                      โ”‚  โ”‚  inconsistent        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The CI validation ensures the committed cache has not been tampered with and matches the lockfile.


Summary

ItemValue
Zero-Installs definitionCommit cache and loaders to Git
Required settingenableGlobalCache: false
Cache location.yarn/cache/
Loader files.pnp.cjs, .pnp.loader.mjs
CI validation--immutable --immutable-cache --check-cache
Native dependency caveatRequires yarn install when added
Yarn 4 defaultDisabled for new projects
AlternativeLazy Installs

Key takeaways:

  • Zero-Installs makes git checkout equivalent to yarn install. The cache and loader files are committed, so the repository is immediately runnable on clone or branch switch .
  • enableGlobalCache: false is mandatory. Without it, Yarn references the machine-global cache, which is not portable across systems .
  • The .gitignore must un-ignore the cache. The default Yarn .gitignore excludes .yarn/* and .pnp.*, which would defeat Zero-Installs .
  • CI must validate the cache. The --check-cache flag verifies that committed packages have not been tampered with, which is critical when accepting external contributions .
  • Native dependencies require yarn install. Packages with compiled binaries cannot run from zip archives; Yarn displays an error if the install is forgotten .
  • Yarn 4 does not enable it by default. New projects use Lazy Installs instead. Zero-Installs is an opt-in pattern for teams that need absolute reproducibility .

Remember: Zero-Installs is a reproducibility pattern, not a performance optimization. It eliminates the install step by committing the dependency artifacts to the repository. The trade-off is repository size. For small projects and internal tools, the trade-off is worth it. For large monorepos, Lazy Installs is the recommended alternative.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!