Skip to content

Bundle Size Tracking

Overview

Bundle size tracking is enforced in CI to prevent performance regressions from JavaScript bundle bloat. The system automatically compares bundle sizes between PRs and the main branch, keeps routine reports in the GitHub Actions job summary, and only creates a sticky PR comment when there is an actionable regression or the analyzer itself fails.

How It Works

CI Workflow

The .github/workflows/bundle-analysis.yml workflow runs on:

  • Push to main: Establishes baseline bundle sizes when bundle-affecting files change
  • Pull requests: Compares against baseline for bundle-affecting changes, updates the job summary, and only comments when action is needed

Workflow Steps

  1. Build with Analyzer: Runs ANALYZE=true npm run build
  2. Generates interactive HTML reports via @next/bundle-analyzer
  3. Creates JSON summary via scripts/report-bundle-size.js

  4. Upload Artifacts: Stores bundle analysis for 30 days

  5. __bundle_analysis.json - Size data
  6. client.html - Interactive client bundle visualization
  7. nodejs.html - Interactive server bundle visualization

  8. Download Baseline: (PR only) Fetches main branch bundle sizes

  9. Compare Sizes: Runs scripts/compare-bundle-size.js

  10. Compares global bundle sizes (raw & gzipped)
  11. Identifies page-level changes
  12. Generates markdown diff report

  13. Sync PR Warning Comment: Only for actionable regressions or analyzer failures

  14. Routine passes do not create inbox-noise comments
  15. Existing warning comments are updated in place, not recreated
  16. Warning comments are deleted automatically after the regression is resolved

  17. Create Job Summary: GitHub Actions summary with results

Bundle Analysis Report

Example Output

## 📦 Bundle Size Analysis

### Global Bundle

| Metric  | Current | Baseline | Diff               |
| ------- | ------- | -------- | ------------------ |
| Raw     | 1.2 MB  | 1.15 MB  | ⚠️ +50 KB (+4.35%) |
| Gzipped | 350 KB  | 340 KB   | ⚠️ +10 KB (+2.94%) |

### Status

⚠️ Action required: detected a significant gzipped bundle regression.

### ⚠️ Significant Regressions

| Page         | Current (gzip) | Baseline (gzip) | Diff                |
| ------------ | -------------- | --------------- | ------------------- |
| `/dashboard` | 45 KB          | 35 KB           | ⚠️ +10 KB (+28.57%) |

### 📊 Largest Pages (Top 5)

| Page         | Size (gzip) | Diff                |
| ------------ | ----------- | ------------------- |
| `/search`    | 60 KB       | ✅ -5 KB (-7.69%)   |
| `/dashboard` | 45 KB       | ⚠️ +10 KB (+28.57%) |
| `/`          | 40 KB       | 📊 +0 KB (+0%)      |

Indicators

  • ⚠️ Warning: Size increased significantly (>10 KB or >5%)
  • ✅ Improvement: Size decreased
  • 📊 Neutral: Minor or no change

Notification Behavior

  • No PR comment: No actionable bundle regression was detected
  • Sticky PR comment: A significant gzipped bundle regression needs attention
  • Failed workflow + sticky PR comment: The comparison automation itself failed and needs investigation

Thresholds

Warning Triggers

Bundle size warnings appear when changes exceed:

  • Absolute: +10 KB increase
  • Percentage: +5% increase

Critical Thresholds (Future)

Currently informational only. Future enhancement may block PRs when:

  • Global bundle increases >20 KB
  • Any page increases >50%

Running Locally

Generate Bundle Report

npm run analyze

This will:

  1. Build with bundle analyzer enabled
  2. Generate HTML reports in .next/analyze/
  3. Open interactive visualizations in browser

View Bundle Analysis

After building, open:

  • .next/analyze/client.html - Client-side bundle breakdown
  • .next/analyze/nodejs.html - Server-side bundle breakdown

Compare Against Baseline

# Build current branch
npm run analyze

# Generate JSON report
node scripts/report-bundle-size.js

# Compare (requires baseline from main branch)
node scripts/compare-bundle-size.js

Note: Local comparison requires manually storing a baseline from main branch.

Best Practices

Keeping Bundles Small

  1. Use Dynamic Imports
// Before: Static import (always loaded)
import HeavyComponent from "./HeavyComponent"

// After: Dynamic import (loaded on demand)
const HeavyComponent = dynamic(() => import("./HeavyComponent"))
  1. Optimize Package Imports
// Bad: Imports entire library
import { Button } from "huge-ui-library"

// Good: Direct import (tree-shakeable)
import Button from "huge-ui-library/button"
  1. Check Dependency Size
npx bundlephobia <package-name>
  1. Use Next.js Optimizations
  2. Already configured in next.config.ts:
    • optimizePackageImports for Radix UI and Lucide
    • Compression enabled
    • Production source maps disabled

Investigating Size Increases

If bundle size increases unexpectedly:

  1. Check New Dependencies
git diff main package.json
  1. View Bundle Composition
  2. Download client.html artifact from CI
  3. Look for unexpectedly large chunks

  4. Identify Duplicate Packages

npm ls <package-name>
  1. Analyze Import Statements
  2. Search for barrel imports (import * as)
  3. Check for accidental server-side imports in client components

Configuration

Bundle Analyzer Config

Located in next.config.ts:

const withAnalyzer = withBundleAnalyzer({
  enabled: process.env.ANALYZE === "true",
  openAnalyzer: false, // Don't auto-open browser in CI
})

Workflow Configuration

Located in .github/workflows/bundle-analysis.yml:

  • Artifact Retention: 30 days
  • Permissions: Read contents, write sticky PR warning comments
  • Concurrency: Cancels older in-flight bundle runs for the same PR/branch
  • Triggers: Push to main, all PRs

Comparison Script

Located in scripts/compare-bundle-size.js:

  • Warn Threshold: 10 KB or 5% increase
  • Output: .next/analyze/bundle-diff.md

Troubleshooting

"No baseline for comparison" in PR

Cause: First PR after enabling tracking, or baseline expired (>30 days).

Solution: Merge PR to establish baseline. Future PRs will compare against it.

No PR comment appeared

Cause: This is the expected quiet path. Routine bundle checks now stay in the job summary unless a significant gzipped regression or analyzer failure is detected.

Solution: No action needed. Open the workflow summary or artifacts if you want the full report.

Workflow failing on "Compare bundle sizes"

Cause: compare-bundle-size.js script error.

Solution: Check job logs and the sticky warning comment link. The workflow fails in this case so the broken automation is visible.

Bundle size looks wrong

Cause: May include development code or source maps.

Solution: Ensure building with NODE_ENV=production (CI does this automatically).

Missing HTML reports in artifacts

Cause: @next/bundle-analyzer only generates HTML when ANALYZE=true.

Solution: Verify workflow uses ANALYZE=true npm run build.

Future Enhancements

Potential improvements to consider:

  1. Automated Bundle Budget Enforcement
  2. Fail CI if bundle exceeds hard limit
  3. Configurable per-route budgets

  4. Historical Trending

  5. Track bundle size over time
  6. Visualize trends in dashboard

  7. Dependency Impact Analysis

  8. Show size contribution of each dependency
  9. Suggest lighter alternatives

  10. Performance Budget Integration

  11. Link bundle size to Lighthouse scores
  12. Track correlation with load time metrics