Caching JS dependencies in Actions: npm vs pnpm vs Yarn vs bun
JavaScript dependency caching in GitHub Actions is one line — if you know which line, and the answer differs by package manager. The npm case is trivial, pnpm has an ordering trap, Yarn depends on which Yarn, and bun quietly caches nothing at all. Here's each one, correct.
npm: the one-liner
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'Caches npm's download cache keyed on package-lock.json. npm ci still runs — it just stops re-downloading. Don't cache node_modules itself: it's platform- and Node-version-shaped, and a stale one survives lockfile edits in confusing ways. Cache the store, reinstall from it.
pnpm: order matters
cache: 'pnpm' works, but setup-node resolves the store location by running pnpm store path — so pnpm must exist before setup-node runs, or you get Unable to locate executable file: pnpm:
- uses: pnpm/action-setup@v4 # FIRST — installs pnpm
- uses: actions/setup-node@v4 # THEN node, which can now query the store
with:
node-version: 22
cache: 'pnpm'This is exactly how vite's CI does it — pnpm/action-setup first, then setup-node with cache: 'pnpm'.
Yarn: which Yarn?
cache: 'yarn' handles Classic (v1) out of the box. Yarn Berry (v2+) via corepack also works — setup-node detects the version — but enable corepack first (corepack enable) and make sure your packageManager field is set so CI and local resolve the same Yarn. If you use Berry's enableGlobalCache: false with a checked-in .yarn/cache, you don't need a CI cache at all — the repo is the cache.
bun: setup-bun caches nothing
oven-sh/setup-bun installs the bun binary — it does not cache your dependencies, and there's no cache: option to turn on. You need an explicit actions/cache step on bun's install cache. GitSpider's own CI is a bun app, so this is exactly what we run:
- uses: oven-sh/setup-bun@v2
- uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
restore-keys: ${{ runner.os }}-bun-The mistake all four share
Caching node_modules directly with a key that never changes. It "works" until a lockfile edit silently serves stale packages. Key on the lockfile hash, cache the package manager's store, and let install re-link — the general caching guide covers keys and restore-keys in depth. See how popular JavaScript repos run CI →
These hide across however many workflow files you have, which is exactly why nobody sits down and fixes them. Point GitSpider at your repo and it flags which patterns apply, with the fix for each.
No signup. Public repos only. ~30 seconds.