Web performance isn't "let's keep it fast" — it's a number that drives decisions. In 2026, INP (which replaced FID) dropping below 200ms prevents a 15–20% conversion rate decline on mobile (Google Chrome UX Report 2025 cohort). To hold this line, you need automated CI pipeline gates, not hope. When setting up Lighthouse CI, RUM, and regression alarms, which thresholds go where? Which metric controls which decision? This post maps performance budgets from test gates to decision architecture, with concrete numbers.
What Is a Performance Budget, and How Does It Tie to Sprint Planning
A performance budget is the ceiling on load time, bundle size, and runtime metrics. Total JS stays under 300KB, FCP never exceeds 1.2s, INP stays below 200ms — these are budgets. You set them at sprint kickoff; they become PR merge criteria. If a new feature blows past these bounds, you either refactor it, defer the feature, or update the budget (accepting conversion loss).
Three sources define budget thresholds: (1) Google's Core Web Vitals baselines (LCP <2.5s, INP <200ms, CLS <0.1), (2) RUM p75 benchmarks from your own traffic (if 75% of your users stay below a threshold, "good" is real), (3) conversion correlation reports (if LCP increases 100ms, conversion drops 2%, then moving the edict from 2.5s to 3s costs you ~10% revenue). A budget isn't one number—it's metric by metric:
| Metric | Threshold | Source |
|---|---|---|
| LCP | <2.5s | CWV official |
| INP | <200ms | CWV 2024+ |
| CLS | <0.1 | CWV official |
| Total JS | <300KB gzip | HTTP Archive p75 |
| FCP | <1.8s | Internal RUM |
You write this table into performance.config.json, Lighthouse CI reads it, and any PR breach fails the check.
Lighthouse CI: Performance as a Merge Gate
Lighthouse CI is a tool that runs Lighthouse audits on every PR, comparing results against your budget file (open-source, Google-backed). It integrates with GitHub Actions, GitLab CI, CircleCI. The flow: (1) PR opens, (2) CI builds, (3) lhci autorun visits test URLs, (4) Lighthouse scores face off against performance.config.json thresholds, (5) breach = PR fails, merge blocked.
Example config (.lighthouserc.json):
{
"ci": {
"collect": {
"url": ["http://localhost:3000/", "http://localhost:3000/product/sample"],
"numberOfRuns": 3
},
"assert": {
"preset": "lighthouse:no-pwa",
"assertions": {
"largest-contentful-paint": ["error", {"maxNumericValue": 2500}],
"interactive": ["error", {"maxNumericValue": 3500}],
"cumulative-layout-shift": ["error", {"maxNumericValue": 0.1}],
"total-byte-weight": ["warn", {"maxNumericValue": 307200}]
}
},
"upload": {
"target": "temporary-public-storage"
}
}
}
LCP >2.5s kills the build. Total bytes >300KB triggers a warning (doesn't block merge, but shows up in logs). Three runs average out network variance. Trade-off: Lighthouse CI runs on local test servers and can't replicate production CDN behavior. Results are "worst case," though production often performs better—still, don't let thresholds slip.
Lighthouse CI + Vercel Preview: Real Staging Tests
Vercel/Netlify auto-spin preview URLs for every PR. Point Lighthouse CI at preview URLs and you test in production-like conditions. GitHub Actions example:
- name: Run Lighthouse CI
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_TOKEN }}
run: |
npm install -g @lhci/cli
lhci autorun --collect.url=${{ steps.vercel.outputs.preview-url }}
The preview-url output from Vercel action plugs in. Now you test real CDN caching, edge SSR, and image optimization. Failed checks post a GitHub comment; add a Slack webhook for team notifications.
RUM: Calibrating Budgets from Real User Data
Lighthouse CI is synthetic—controlled environment, consistent network. RUM (Real User Monitoring) is actual traffic. The gap matters: Lighthouse simulates 3G throttling; RUM captures 4G/5G/fiber mixes. Lighthouse cold-cache tests; RUM shows repeat-visitor cache wins. Budget off Lighthouse alone and you miss actual user reality.
Use Google's Web Vitals library for RUM collection. Each page load measures CWV metrics and beacons them to your endpoint. Example:
import {onCLS, onINP, onLCP} from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
rating: metric.rating
});
navigator.sendBeacon('/analytics', body);
}
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
Your backend logs /analytics events to BigQuery (or your warehouse). Compute p75:
SELECT
APPROX_QUANTILES(value, 100)[OFFSET(75)] AS p75_lcp
FROM metrics
WHERE name = 'LCP' AND date >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY);
If p75 is 2.8s but your budget is 2.5s, either raise the budget (accepting conversion dip) or optimize. p75 works because Google scores CWV off p75 too—it's the benchmark that matters to rankings.
RUM + Segmentation: Device and Region Budgets
One global budget doesn't fit all traffic. Mobile LCP runs ~40% higher than desktop (Chrome UX 2025), India traffic is ~60% slower than US. Segment RUM data to differentiate budgets:
| Segment | LCP Budget | INP Budget |
|---|---|---|
| Desktop | 2.2s | 180ms |
| Mobile | 3.0s | 220ms |
| India | 3.5s | 250ms |
Add deviceType and country to your RUM beacon (GeoIP lookup on backend), group BigQuery queries by segment, set region-specific thresholds. Lighthouse CI doesn't natively support multi-config, but you can run separate workflows (lhci-mobile.json, lhci-desktop.json).
Regression Alarms: Performance Drops Hit Slack Instantly
Budget set, CI gates active, RUM rolling—but what if production regresses post-deploy? New code ships, LCP jumps from 2.3s to 2.9s. Rather than catch it 3 hours later, alarm in 5 minutes. Set a job (Cloudflare Workers Cron, AWS Lambda EventBridge, GCP Cloud Scheduler) to analyze RUM every 5 minutes.
Alarm logic (pseudo-code):
// Runs every 5 minutes
async function checkRegression() {
const current = await query('SELECT AVG(value) FROM metrics WHERE name="LCP" AND timestamp > NOW() - INTERVAL 5 MINUTE');
const baseline = await query('SELECT AVG(value) FROM metrics WHERE name="LCP" AND timestamp BETWEEN NOW() - INTERVAL 1 DAY AND NOW() - INTERVAL 1 HOUR');
if (current > baseline * 1.15) { // 15% jump
await sendSlack({
text: `🚨 LCP regression: ${current}ms (baseline ${baseline}ms)`,
channel: '#performance-alerts'
});
}
}
Baseline spans 1 hour back because a deploy may have just shipped. 15% threshold requires calibration—10% is noise, 25% is late. Wire to PagerDuty or Opsgenie for on-call. Team rolls back or hot-fixes on alert.
Root-Cause Regression: Lighthouse Diff
Alert fires, LCP spiked—why? Lighthouse CI just gates thresholds; it doesn't explain root cause. Use Lighthouse Diff to compare two builds:
lhci compare --base=build-1234 --head=build-1235 --preset=lighthouse:all
Output: "unused-javascript increased by 45KB", "server-response-time +120ms". These deltas narrow the search. Run webpack-bundle-analyzer or Next.js analyze to find the 45KB culprit. Check server traces for the 120ms lag source.
Linking Performance to Conversion: Attribution Model
Budgets are technical numbers. To wire them into business decisions, translate to revenue impact. You need a report: "raising LCP from 2.5s to 3s costs ~4% conversion." Build this with A/B test or cohort analysis.
A/B test: serve 50% of traffic a deliberately slower build (inject 500ms Lighthouse trace delay), measure conversion lift. Cohort analysis: RUM data + GA4 export; compute conversion rate bucketed by LCP range.
SQL for Google Analytics 4 + BigQuery:
SELECT
CASE
WHEN lcp < 2000 THEN 'fast'
WHEN lcp BETWEEN 2000 AND 4000 THEN 'medium'
ELSE 'slow'
END AS lcp_bucket,
COUNT(DISTINCT user_pseudo_id) AS users,
COUNTIF(event_name = 'purchase') / COUNT(DISTINCT session_id) AS conversion_rate
FROM analytics_events
LEFT JOIN rum_metrics ON analytics_events.session_id = rum_metrics.session_id
GROUP BY lcp_bucket;
Output:
| LCP Bucket | Conversion Rate |
|---|---|
| fast | 4.2% |
| medium | 3.6% |
| slow | 2.9% |
Now LCP budget ROI is clear: shrinking from 3s to 2.5s lifts conversion from 3.6% to 4.2%, a +16.7% bump. With 100K monthly visitors, that's +1,670 conversions; at $50 AOV, +$83K revenue. Present this to the CFO (not the CTO) to justify perf sprint priority.
Budget Breach: Tradeoff Decision
A new feature ships and bundles up 50KB. Budget breaks. Three paths: (1) refactor the code (code-split, lazy load), (2) raise the budget and accept conversion loss, (3) defer the feature. Base the call on numbers.
The 50KB increase adds ~200ms to LCP (from Lighthouse trace). The RUM cohort model says +200ms LCP = –0.8% conversion hit. If the feature delivers +5% lift, net gain is +4.2%—ship it. If it's +1% lift, net loss is –0.2%—defer.
Build a "performance cost estimator" (internal tool). Input: bundle delta. Output: estimated LCP delta + conversion impact. Simple regression model: every 10KB bundle = +30ms LCP; every 100ms LCP = –0.8% conversion (calibrated from your RUM data). Show PMs the tradeoff; it shapes roadmap priority.
Headless Commerce: Wiring Budget to Product Velocity
In e-commerce, performance = revenue. Headless architectures (Shopify Hydrogen, Remix, Next.js) put frontend bundle in your hands but backend API latency in your budget too. Shopify Storefront API averages 150ms response; add that to the budget: LCP = TTFB (150ms) + FCP (800ms) + LCP gap (600ms) = 1550ms. Budget 2500ms leaves 950ms wiggle room.
Regression sources in headless: (1) API query complexity grows (GraphQL depth +2 levels = +50ms), (2) SSR component count inflates (20 components = +100ms hydration), (3) third-party scripts pile on (analytics tag = +200ms). Lighthouse CI can't tease these apart; you need RUM traces. Emit Server-Timing headers from Next.js Middleware:
export function middleware(req) {
const start = Date.now();
const res = NextResponse.next();
res.headers.set('Server-Timing', `api;dur=${Date.now() - start}`);
return res;
}
Chrome DevTools Network tab shows Server-Timing. Fold it into RUM beacons, set regression alarms.
Tying performance budgets to decisions requires three layers: (1) Lighthouse CI gates in CI/CD, (2) RUM calibration against real user baselines, (3) regression alarms + conversion correlation to link business impact. Budgets aren't monolithic—they're segmented by device and region. When a budget breaches, run tradeoff math: does the feature's lift beat the perf loss? In headless shops, TTFB breakdown sits in the budget alongside front-end metrics. Performance isn't "let's be fast." It's a conversion input you measure, gate, and optimize.