Skip to content

PNPM Optimization Strategies

This document outlines the PNPM optimization strategies implemented across our GitHub Actions workflows to dramatically improve dependency installation performance and CI execution times.

We’ve upgraded from pnpm/action-setup@v2 to pnpm/action-setup@v4 for improved performance and latest features.

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
run_install: false

Our composite action implements manual PNPM store caching for better control and performance:

- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm store cache
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ component }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-${{ component }}-
${{ runner.os }}-pnpm-store-

Dependencies are installed with performance-focused flags:

Terminal window
pnpm install --frozen-lockfile --prefer-offline --no-audit

Flag explanations:

  • --frozen-lockfile: Ensures exact reproducibility and faster installs
  • --prefer-offline: Uses cached packages when available, avoiding unnecessary network calls
  • --no-audit: Skips security audits during CI for faster execution

We’ve created a reusable composite action at .github/actions/pnpm-setup/action.yml that eliminates duplication across workflows:

- name: Setup PNPM with optimized caching
uses: ./.github/actions/pnpm-setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
cache-strategy: 'manual'
component: ${{ matrix.component }}

Cache Strategy Options:

  • manual: Uses optimized PNPM store caching (recommended for most cases)
  • builtin: Uses actions/setup-node@v4 built-in PNPM caching (simpler but less control)

Based on comprehensive research and implementations by other teams:

  • Speed Improvements: Teams report reducing deployment times from 26 minutes to 1 minute (25x improvement)
  • Network Optimization: --prefer-offline flag alone provides 15-20% speed improvements
  • Cache Efficiency: Component-specific cache keys improve cache hit rates

The workflows include basic execution time reporting. For detailed performance monitoring:

  1. Check workflow run duration in GitHub Actions
  2. Monitor cache hit rates in workflow logs
  3. Compare before/after implementation metrics

The composite action supports component-specific cache keys for better cache isolation:

component: ${{ matrix.component }} # Creates component-specific cache keys

Always pin PNPM versions to avoid lockfile compatibility issues:

env:
PNPM_VERSION: '9' # Matches packageManager field in package.json

The following workflows have been optimized:

  • Main CI (.github/workflows/ci.yml): All jobs (typecheck, build, lint, format)
  • Storybook Tests (.github/workflows/storybook-tests.yml): UI component testing
  • Knip (.github/workflows/knip.yml): Unused code detection

Cache keys use precise patterns for optimal cache invalidation:

key: ${{ runner.os }}-pnpm-store-${{ component }}-${{ hashFiles('**/pnpm-lock.yaml') }}

This ensures:

  • New caches are created only when dependencies actually change
  • Component isolation prevents cache conflicts
  • Fallback patterns allow incremental improvements

Keep Node.js and PNPM versions aligned between local and CI environments to prevent lockfile compatibility issues.

Always use pnpm store path --silent to get the correct store directory rather than hardcoding paths.

Include restore-keys to allow partial cache matches, enabling incremental improvements even with lockfile changes.

For workspaces, the composite action automatically handles dependency caching per component for optimal performance.

Frozen Lockfile Errors: Ensure PNPM versions match between local and CI environments. Check the packageManager field in your root package.json.

Cache Misses: Verify that pnpm-lock.yaml is committed and unchanged. Check cache key patterns in workflow logs.

Network Issues: For self-hosted runners, consider local caching solutions as GitHub’s remote cache can be slower over public internet connections.

  1. Check cache hit/miss status in workflow logs
  2. Verify store path detection: pnpm store path --silent
  3. Monitor actual dependency installation times
  4. Compare cache key patterns between workflows

Potential additional improvements:

  1. Conditional Installation: Skip reinstallation when cache is hit perfectly
  2. Workspace-Specific Optimization: Further optimize for monorepo workspace dependencies
  3. Build Artifact Caching: Enhanced caching for build outputs and TypeScript incremental builds
  4. Performance Monitoring: Automated performance regression detection