// Frontend
Web Performance Budgets and Core Web Vitals in Practice
Fix LCP, INP and CLS for real users, then keep them fixed with a performance budget that fails the build when a regression slips in.
Md Nayeem HossainJul 21, 20253 min read// Frontend
Fix LCP, INP and CLS for real users, then keep them fixed with a performance budget that fails the build when a regression slips in.
Md Nayeem HossainJul 21, 20253 min readPerformance work has a frustrating pattern. A team spends a sprint making the site fast, scores improve, everyone celebrates — and six months later the site is slow again. Nobody made it slow on purpose. It got slow one small dependency and one unoptimized image at a time.
The fix is not another optimization sprint. It is a performance budget enforced in CI, built around the metrics that actually reflect user experience.
Google's Core Web Vitals are a practical, user-centered set of measurements:
Measure them at the 75th percentile of real users, not on your fast laptop on office Wi-Fi.
Lighthouse in CI is lab data: consistent, repeatable, good for catching regressions. Real user monitoring is field data: messy but true, collected from actual devices and networks.
You need both. Lab tests stop regressions before they ship. Field data tells you whether users are actually having a good experience. A page can score 100 in Lighthouse and still feel slow to users on mid-range Android phones.
Collecting field data is a few lines with the web-vitals package:
1import { onCLS, onINP, onLCP } from "web-vitals";2 3function send(metric: { name: string; value: number; id: string }) {4 navigator.sendBeacon("/api/vitals", JSON.stringify(metric));5}6 7onLCP(send);8onINP(send);9onCLS(send);LCP is usually a hero image or a large heading. The common causes and fixes:
srcset.1<Image src="/hero.avif" alt="" width={1200} height={630} priority sizes="100vw" />INP measures how busy the main thread is when the user interacts. It gets worse as JavaScript grows.
useTransition marks expensive re-renders as interruptible so typing stays responsive.Layout shift is almost always caused by content without reserved space:
width and height (or aspect-ratio) on images and video.font-display: swap with size-adjusted fallback fonts to avoid text reflow.A budget turns performance from an aspiration into a test that can fail. Define limits for the things that cause regressions:
1{2 "budgets": [3 { "resourceType": "script", "budget": 170 },4 { "resourceType": "image", "budget": 300 },5 { "metric": "largest-contentful-paint", "budget": 2500 },6 { "metric": "cumulative-layout-shift", "budget": 0.1 }7 ]8}Run it in CI on every pull request. When a change exceeds the budget, the build fails, and the conversation happens before the regression ships: is this dependency worth 40 KB?
Budgets work best with an owner. Someone reviews the trend monthly and adjusts limits deliberately rather than raising them whenever a build fails.
Put a field-data dashboard where the team sees it: p75 LCP, INP and CLS per key page, over time, broken down by device type. When performance is visible, people notice regressions themselves.
Lasting performance comes from measuring what users feel, fixing the specific causes of each vital, and enforcing a budget so regressions fail in CI instead of in production. Optimization sprints make a site fast once. Budgets keep it fast.