Performance score 20 → 100. The same page, the same markup, the same copy — only the loading changed.
A Lighthouse score is a number nobody acts on. A budget turns it into a decision: this release ships, or it does not. This repository measures both builds of a property-listings page, enforces budgets in CI, and publishes a before-and-after report.
Median of 3 Lighthouse runs, emulated mid-tier mobile over 4G:
| Metric | Legacy build | Remediated build | Budget | Change |
|---|---|---|---|---|
| Performance score | 20 | 100 | ≥ 90 | — |
| Largest Contentful Paint | 9,216 ms | 908 ms | ≤ 2,500 ms | −90% |
| First Contentful Paint | 8,916 ms | 833 ms | ≤ 1,800 ms | −91% |
| Speed Index | 8,916 ms | 833 ms | ≤ 3,400 ms | −91% |
| Total Blocking Time | 1,952 ms | 0 ms | ≤ 200 ms | −100% |
| Cumulative Layout Shift | 0.197 | 0.000 | ≤ 0.1 | −100% |
| Transfer size | 880 KB | 15 KB | ≤ 400 KB | −98% |
7 of 7 budgets breached on the legacy build; 0 of 7 on the remediated one.
Four ordinary mistakes, none of which change how the page looks once it has settled — which is exactly why they survive code review.
| Defect | What it costs | Where |
|---|---|---|
Render-blocking script in <head> that also burns 900 ms of main thread before paint |
FCP, LCP, Speed Index | app/blocking.js |
| 880 KB of parsed-but-unused payload on the critical path | Transfer size, parse time | app/blocking.js |
| Content injected above existing content 600 ms after paint | Cumulative Layout Shift | app/late-content.js |
| Long tasks scheduled after first paint | Total Blocking Time | app/late-content.js |
| Images with no intrinsic dimensions, no lazy loading | Layout stability, wasted bytes | app/slow.html |
The remediated build defers its script, declares image dimensions, lazy-loads below-the-fold media, and ships the notice in the HTML instead of injecting it. Read the two files side by side — the diff is the fix list.
| Capability | Where to look |
|---|---|
| Lighthouse driven programmatically, not by hand | scripts/run-audit.mjs |
| Budgets as data, versioned with the code | budgets/budgets.json |
| A failing budget exits non-zero and fails the build | scripts/run-audit.mjs |
| Median-of-N runs, because a single run is noise | scripts/run-audit.mjs |
| Before/after reporting with gauges and deltas | scripts/build-report.mjs |
| Nightly runs, so drift is caught without a push | ci.yml |
Requires Node 20+ and Chrome.
npm ci
npm run serve & # app under test on :4175
npm run report # audit both builds, enforce budgets, build the reportnpm run audit -- --runs 5 # more samples, less noise
npm run audit -- --preset desktop # desktop form factor
npm run report:build # rebuild the report from the last runThen open report/site/index.html, or compare the builds yourself at /slow.html and /fast.html.
Only the remediated build gates. The legacy build is a fixture, kept so the budgets are proven capable of failing. A budget that has never gone red is a budget nobody has tested.
The median of several runs, not a single sample. Lighthouse timings move by hundreds of milliseconds between runs on a busy machine, and a shared CI runner is noisier than a laptop. A budget built on one sample is a coin toss, so CI defaults to three runs and the flag goes higher when it matters.
Budgets live in version control as data. budgets/budgets.json is reviewable, diffable, and arguable in a pull request. Thresholds buried in a script are none of those things.
The failure message names the number and the limit. A red build prints Total Blocking Time: 1952ms (limit 200ms) — the gap, not just a verdict.
Opportunities are captured, not just scores. The report carries Lighthouse's top suggested fixes with their estimated savings, so a breach arrives as a task list.
Budgets are set at the Core Web Vitals thresholds. LCP ≤ 2.5 s, CLS ≤ 0.1, and a score floor of 90 — the same bar Google applies, rather than numbers chosen to be comfortably passable.
- The application under test ships with this repository, so a measurement cannot be invalidated by someone else's deployment.
- Timings on a shared CI runner are slower than on a laptop. That is the point of budgets rather than fixed expectations: the remediated build clears the bar on both.
- This repository contains no code, data, or credentials from any employer. Every line was written for this portfolio.
- accessibility-audit-lab — WCAG 2.2 AA auditing with annotated findings
- visual-regression-lab — pixel regression with containerised baselines
- playwright-ts-ui-framework — UI automation in Playwright + TypeScript
- rest-api-automation-suite — API testing with contract schemas
- selenium-java-testng-framework — Selenium + TestNG in Java
MIT
