Overview
A cache restores the result of a slow step, such as a dependency download or an image build, when its inputs have not changed. This guide uses the built-in caching in actions/setup-node for packages and actions/cache for build outputs and browsers. The key rule: derive the cache key from a hash of the inputs, never from a timestamp.
Prerequisites
- A workflow in
.github/workflows/and a committed lockfile (package-lock.json,pnpm-lock.yaml, orbun.lock).
Steps
1. Cache dependencies through setup-node
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm # or pnpm, yarn
- run: npm ciThis caches the package manager’s download cache (for npm, ~/.npm), keyed on the lockfile hash. npm ci still runs and still deletes node_modules, so only the downloads are skipped. For pnpm, install pnpm before setup-node so the cache step can find it:
- uses: pnpm/action-setup@v6 # version from "packageManager" (pnpm 11+); else set `version:`
- uses: actions/setup-node@v7
with: { node-version: 22, cache: pnpm }
- run: pnpm install --frozen-lockfilePin pnpm with the packageManager field in package.json so CI and local use the same version. The pnpm docs now also show a pnpm/setup action that installs pnpm and Node together.
2. Cache build outputs
Cache only deterministic outputs. Skip the build step when the exact key hits.
- uses: actions/cache@v6
id: build-cache
with:
path: dist
key: ${{ runner.os }}-build-${{ hashFiles('src/**', 'vite.config.ts') }}
- if: steps.build-cache.outputs.cache-hit != 'true'
run: npm run build3. Cache browser binaries
- uses: actions/cache@v6
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
- run: npx playwright install --with-deps chromiumKeep the install step: it is fast on a hit and necessary on a miss, and --with-deps installs system libraries that the cache does not hold.
4. Use restore-keys for partial hits
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-When the exact key misses, GitHub tries each restore-keys prefix in order and restores the most recent match, so a lockfile change reuses most of the old cache. List keys from most to least specific.
Verify it worked
Run the workflow twice on the same branch. The second run’s cache step logs “Cache restored” and the install step takes seconds instead of minutes.
gh api repos/{owner}/{repo}/actions/cache/usageCommon errors
- The cache never hits. The key contains a timestamp, run ID, or other changing value.
- Stale
node_modules. Cachingnode_modulesdirectly skips the lockfile check; cache the package-manager download cache and still run the install command. - A feature branch misses. Caches are readable from the same branch and the default branch, and pull requests also read the base branch’s; a cache from a sibling feature branch is invisible. Run the workflow once on
main. - Eviction. The default limit is 10 GB per repository, and entries unused for 7 days are removed, oldest first. Narrow keys or delete old caches under Actions, Caches.
- Secrets in a cache. Anyone who can open a pull request can read cached files; never cache
.envfiles or credentials.
FAQ
Should I cache node_modules in GitHub Actions?
No; caching node_modules directly skips the lockfile check. Cache the package manager’s download cache, for example with setup-node’s cache: npm, and still run the install command.
Why does my GitHub Actions cache never hit?
The key contains a timestamp, run ID, or other changing value; derive it from a hash of the inputs, such as hashFiles('package-lock.json').
Why can’t my feature branch read a cache from another branch?
Caches are readable from the same branch and the default branch, and pull requests also read the base branch’s. Run the workflow once on main so sibling branches can restore it.
How big can the GitHub Actions cache get?
The default limit is 10 GB per repository, and entries unused for 7 days are removed, oldest first.
Is it safe to cache .env files in GitHub Actions?
No; anyone who can open a pull request can read cached files, so never cache .env files or credentials.