Skip to content

Performance Budgets

This document specifies the Core Web Vitals (Lighthouse) and bundle-size budgets enforced by the .github/workflows/perf.yml CI gate.

Overview

Performance budgets are enforced on every PR and push to main via:

  1. Lighthouse CI — measures synthetic performance against Core Web Vitals thresholds (LCP, CLS, TBT).
  2. Bundle Size Check — gates total .next/static/chunks size against uncompressed and gzipped budgets.

Both gates fail the workflow on budget breach, triggering a required branch-protection block.


Core Web Vitals (Lighthouse)

Measured synthetically on the homepage (/) via Lighthouse CI running against a booted Next.js app.

Metric Budget Baseline Notes
First Contentful Paint (FCP) ≤ 2500 ms ~1800 ms Lab metric; time to render first content
Largest Contentful Paint (LCP) ≤ 3500 ms ~2400 ms Core Web Vital (lab); primary interaction/rendering metric
Cumulative Layout Shift (CLS) ≤ 0.1 ~0.05 Core Web Vital (lab); visual stability
Total Blocking Time (TBT) ≤ 250 ms ~150 ms Lab proxy for Interaction to Next Paint (INP), which is a field metric. TBT is measured in synthetic runs; INP comes from real-user monitoring. See Web Vitals guide for context.
Performance Score ≥ 75 ~82 Lighthouse performance score

Baseline

Measured on 2026-05-25 with: - Next.js 16.2.6 (Turbopack) - CopilotKit 1.58.0 - React 19.2.6 - 1 run on ubuntu-latest (GitHub Actions)

Raw Lighthouse output:

FCP: 1.8 s
LCP: 2.4 s
CLS: 0.05
TBT: 150 ms
Performance: 82

Updating CWV Budgets

If a legitimate code change increases these metrics:

  1. Measure the new baseline with your change:

    npm run next:build
    npm run next:start &
    npx @lhci/[email protected] autorun --config=lighthouserc.json
    

  2. Update the budget in lighthouserc.json:

    {
      "ci": {
        "assert": {
          "assertions": {
            "largest-contentful-paint": ["error", { "maxNumericValue": <new_budget_ms> }],
            ...
          }
        }
      }
    }
    

  3. Document the change in this file with the date, PR, and reason.


Bundle Size

Measured via scripts/check-bundle-size.js, which sums all .next/static/chunks/*.js files.

Metric Budget Baseline Notes
Total (uncompressed) ≤ 21 MB 18.17 MB Raw JS bytes in .next/static/chunks/
Total (gzipped) ≤ 4.5 MB 4.12 MB Typical over-the-wire size (gzip compression)

Baseline

Measured on 2026-05-25 with: - Next.js 16.2.6 (Turbopack) - CopilotKit 1.58.0 - React 19.2.6

Script output:

Total chunks (uncompressed): 18.17 MB
Budget (uncompressed): 21.00 MB

Total chunks (gzipped): 4.12 MB
Budget (gzipped): 4.50 MB

✓ All bundle-size budgets passed

Updating Bundle Budgets

If a change legitimately increases bundle size (e.g., adding a new dependency):

  1. Build and measure:

    MIDDLE_CORE_URL=http://127.0.0.1:9 npm run next:build
    npm run perf:bundle
    

  2. Update budgets in scripts/check-bundle-size.js:

    const budgets = {
      totalChunksUncompressed: <new_budget_bytes>,
      totalChunksGzipped: <new_budget_bytes>,
    };
    

  3. Document the change here with the date, PR, and reason.


CI Workflow (.github/workflows/perf.yml)

The performance gate runs two jobs:

1. Bundle Size Gate

  • Builds the Next.js app
  • Runs npm run perf:bundle (fails on budget breach)

2. Lighthouse (Core Web Vitals)

  • Builds the Next.js app
  • Boots next start (same pattern as e2e.yml)
  • Installs Chromium via Playwright
  • Runs Lighthouse CI against http://localhost:3000/
  • Uploads report as artifact (always)

Both gates are required branch-protection gates; any failure blocks merge.


Troubleshooting

"Lighthouse CI failed to run"

  • Ensure Chromium is available: npx playwright install --with-deps chromium
  • Check Next.js is running: curl -s http://localhost:3000
  • Review .github/workflows/perf.yml for Chrome path detection

"Bundle size exceeded"

  • Run npm run perf:bundle locally to confirm
  • Analyze what changed: git diff HEAD -- app/ package.json
  • If unavoidable, raise the budget and document in this file

"CWV budget breached"

  • Re-run locally: npm run next:build && npm run next:start
  • Profile with Chrome DevTools (npm run next:dev and F12)
  • Look for:
  • New large dependencies (check /api/** route handlers)
  • Unoptimized images or fonts
  • Blocking third-party scripts
  • Layout thrashing in component renders
  • If unavoidable, raise the budget and document the reason here

  • .github/workflows/perf.yml — CI workflow definition
  • lighthouserc.json — Lighthouse CI configuration (assertions, URL, headless flags)
  • scripts/check-bundle-size.js — Bundle size check implementation
  • .next/static/chunks/ — Build output (measured by budget gate)