Skip to content

How size-limit Catches Bundle Regressions Before They Ship

Catching Bundle Regressions Before They Ship

A CI setup using size-limit that fails a PR when bundle size regresses, catching what a manual bundle-analyzer check only finds after the damage ships.

· · 4 min read

Quick Take

A single unnoticed dependency bumped our main bundle by 180KB, and nobody caught it until a user complained about load time three weeks later. A size budget in CI would have failed that PR on the spot.

The 180KB regression that prompted this setup came from a single dependency version bump, a patch release that pulled in a new transitive dependency neither the PR author nor the reviewer noticed in a diff full of otherwise-unrelated package.json changes. A human review missed it because bundle size isn't something a diff makes visible. A CI check would have.

TL;DR: size-limit runs in CI and fails a pull request when a bundle's gzipped size exceeds a configured budget, catching regressions at review time instead of after they ship. Set the budget from your current measured size plus 10-20% headroom, not an arbitrary number, and check gzip size by default since that's what's actually transferred to users.

Setting Up size-limit

npm install --save-dev size-limit @size-limit/preset-app
// package.json
{
  "size-limit": [
    {
      "name": "Main bundle",
      "path": "dist/assets/index-*.js",
      "limit": "180 KB"
    },
    {
      "name": "Vendor bundle",
      "path": "dist/assets/vendor-*.js",
      "limit": "120 KB"
    }
  ],
  "scripts": {
    "size": "size-limit"
  }
}

Each entry names a specific output chunk and its budget. Splitting the budget by chunk (main app code versus vendor/dependency code) rather than one combined number makes a regression easier to diagnose from the CI failure alone, "vendor bundle exceeded its limit" points straight at a dependency change, where a single combined budget would just say "something got bigger" without narrowing down where.

Wiring It Into GitHub Actions

# .github/workflows/size-check.yml
name: Bundle Size Check

on:
  pull_request:
    branches: [main]

jobs:
  size:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npm run build
      - name: Check bundle size
        run: npx size-limit

npx size-limit exits with a non-zero code if any chunk exceeds its budget, which fails the CI job and blocks the PR from being merged (assuming this check is set as required in the repo's branch protection rules). The failure message names the specific chunk and by how much it exceeded the limit, giving the PR author a concrete number to investigate instead of a vague "the bundle got bigger" complaint days later.

Posting the Size Diff as a PR Comment

The size-limit GitHub Action posts a comparison comment directly on the PR, showing the size delta against the base branch, which is more actionable during review than a pass/fail check alone:

# Add this step to the same size-check.yml workflow
- uses: andresz1/size-limit-action@v1
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}

This surfaces a table like Main bundle: 174.2 KB (+3.1 KB, +1.8%) directly in the PR conversation, so a reviewer sees the size impact of the change without needing to run anything locally or dig through a separate CI log.

Choosing the Budget Number

npx size-limit
# Main bundle: 162.4 KB / 180 KB (90%)

Run this against your current main branch first to get a real baseline, then set the budget at roughly 110-120% of that measured number, not a round figure picked without reference to the actual codebase. A budget set with no headroom fails on routine, legitimate growth (a new feature that genuinely needs a few more KB) and trains the team to bump the limit reflexively rather than treating a failure as a signal worth investigating. A budget with too much headroom, on the other hand, catches nothing until the bundle has already grown far past where anyone would want it.

What size-limit Doesn't Catch

A per-chunk budget catches growth in the chunks you've configured, it won't catch a new chunk appearing that wasn't in the config at all (a route added without route-level code-splitting, landing entirely inside the main bundle instead of its own chunk), or a regression in a chunk you haven't budgeted. Review the size-limit config itself periodically as the app's chunk structure evolves, an outdated config silently stops protecting the parts of the bundle that changed shape since it was last written.

Old Way vs Modern Way

ApproachWhen it catches a regressionEffort to check
Manual bundle analyzerWhenever someone remembers to run itManual, easy to skip
Lighthouse CI performance scoreAfter deploy, aggregated with other metricsAutomated, but not bundle-specific
size-limit in CIAt PR review, before mergeAutomated, zero ongoing effort once configured

Conclusion

A bundle size regression is invisible in a normal code review, package.json diffs don't show the transitive dependency tree, and a 180KB jump from a single patch bump reads the same in a diff as a 2KB one. size-limit in CI turns that invisible number into an explicit, enforced check at exactly the point where catching it costs the least, before the PR merges, not three weeks after a user notices.

Frequently Asked Questions

What's the difference between size-limit and a manual bundle analyzer check?
A bundle analyzer (webpack-bundle-analyzer, vite-bundle-visualizer) is a tool you run manually to inspect what's in a bundle, useful for investigating a known problem. size-limit is a CI-integrated tool that enforces a numeric budget automatically on every pull request, failing the build if a bundle exceeds its configured limit, so a regression is caught at the PR review stage instead of relying on someone remembering to check a bundle analyzer periodically.
How do I pick the right size budget number for size-limit?
Start from your current bundle size, measured with size-limit itself, and set the budget slightly above that (10-20% headroom) rather than an arbitrary round number. A budget set too tight fails on routine, legitimate growth and gets ignored or bypassed; a budget set too loose catches nothing. Revisit the number deliberately every quarter or so as the app grows, rather than letting it silently ratchet up every time a PR needs a one-off bump.
Does size-limit measure gzipped or raw bundle size?
By default, size-limit measures gzip-compressed size, which reflects what's actually transferred over the network for most production deployments (nearly all CDNs and servers gzip or brotli-compress JavaScript automatically). It can be configured to measure brotli or raw size instead, but gzip is the right default for a budget meant to approximate real download weight.