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.
Key Optimization Strategies
Section titled “Key Optimization Strategies”1. Updated PNPM Action Version
Section titled “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: false2. Optimized PNPM Store Caching
Section titled “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
Section titled “3. Optimized Installation Flags”Dependencies are installed with performance-focused flags:
pnpm install --frozen-lockfile --prefer-offline --no-auditFlag 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
Section titled “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: Usesactions/setup-node@v4built-in PNPM caching (simpler but less control)
Performance Impact
Section titled “Performance Impact”Expected Improvements
Section titled “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-offlineflag alone provides 15-20% speed improvements - Cache Efficiency: Component-specific cache keys improve cache hit rates
Monitoring Performance
Section titled “Monitoring Performance”The workflows include basic execution time reporting. For detailed performance monitoring:
- Check workflow run duration in GitHub Actions
- Monitor cache hit rates in workflow logs
- Compare before/after implementation metrics
Configuration Options
Section titled “Configuration Options”Component-Specific Caching
Section titled “Component-Specific Caching”The composite action supports component-specific cache keys for better cache isolation:
component: ${{ matrix.component }} # Creates component-specific cache keysVersion Consistency
Section titled “Version Consistency”Always pin PNPM versions to avoid lockfile compatibility issues:
env: PNPM_VERSION: '9' # Matches packageManager field in package.jsonImplementation Details
Section titled “Implementation Details”Workflows Updated
Section titled “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
Section titled “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
Section titled “Best Practices”1. Version Alignment
Section titled “1. Version Alignment”Keep Node.js and PNPM versions aligned between local and CI environments to prevent lockfile compatibility issues.
2. Cache Directory Precision
Section titled “2. Cache Directory Precision”Always use pnpm store path --silent to get the correct store directory rather than hardcoding paths.
3. Fallback Strategy
Section titled “3. Fallback Strategy”Include restore-keys to allow partial cache matches, enabling incremental improvements even with lockfile changes.
4. Monorepo Optimization
Section titled “4. Monorepo Optimization”For workspaces, the composite action automatically handles dependency caching per component for optimal performance.
Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “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
Section titled “Debugging Cache Performance”- Check cache hit/miss status in workflow logs
- Verify store path detection:
pnpm store path --silent - Monitor actual dependency installation times
- Compare cache key patterns between workflows
Future Optimizations
Section titled “Future Optimizations”Potential additional improvements:
- Conditional Installation: Skip reinstallation when cache is hit perfectly
- Workspace-Specific Optimization: Further optimize for monorepo workspace dependencies
- Build Artifact Caching: Enhanced caching for build outputs and TypeScript incremental builds
- Performance Monitoring: Automated performance regression detection