NexisChat Docs

PNPM Optimization Strategies

Performance optimizations for PNPM dependency installation in GitHub Actions workflows.

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

Key Optimization Strategies

1. Updated PNPM Action Version

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

2. Optimized PNPM Store Caching

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-

3. Optimized Installation Flags

Dependencies are installed with performance-focused flags:

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

4. Composite Action for DRY Principle

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)

Performance Impact

Expected Improvements

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

Monitoring Performance

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

Configuration Options

Component-Specific Caching

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

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

Version Consistency

Always pin PNPM versions to avoid lockfile compatibility issues:

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

Implementation Details

Workflows Updated

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 Key Strategy

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

Best Practices

1. Version Alignment

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

2. Cache Directory Precision

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

3. Fallback Strategy

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

4. Monorepo Optimization

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

Troubleshooting

Common Issues

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.

Debugging Cache Performance

  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

Future Optimizations

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