Skip to content

Lesson 9 of 9 · 22m

Guardrails: Keeping It Fast After You Fix It

Performance decays by default. Lab budgets in CI, field-data regression alerts, third-party governance, and the team habits that keep the gains from evaporating.

Every performance project I've seen succeed has the same epilogue: six months later, half the gains are gone. Nobody made a bad decision. Marketing added a heatmap tool, a new carousel shipped with lazy-loaded slides, someone imported a full utility library for one function. Performance is a property that decays by default — the fix isn't another project, it's guardrails that make regressions loud at the moment they're introduced.

Layer one: lab assertions on every PR

Lighthouse CI runs a production-like build against key routes and fails the check when metrics or resource sizes cross a line. Lab numbers are noisy, so assert on medians of several runs and leave margin.

{
  "ci": {
    "collect": {
      "startServerCommand": "npm run preview -- --port 4173",
      "url": [
        "http://localhost:4173/",
        "http://localhost:4173/product/trail-runner-3",
        "http://localhost:4173/search?q=jacket"
      ],
      "numberOfRuns": 5
    },
    "assert": {
      "assertions": {
        "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
        "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
        "total-blocking-time": ["warn", { "maxNumericValue": 300 }],
        "resource-summary:script:size": ["error", { "maxNumericValue": 180000 }],
        "resource-summary:third-party:count": ["warn", { "maxNumericValue": 8 }]
      }
    },
    "upload": { "target": "temporary-public-storage" }
  }
}
npx @lhci/cli@0.13.x autorun --config=./lighthouserc.json

Pair it with the size-limit gate from lesson 4: size checks are deterministic and fast, so they catch bundle growth reliably; Lighthouse catches the things size can't — a lazy hero, a render-blocking stylesheet, a layout shift.

Layer two: field alerts per release

Lab CI can't see real devices, A/B variants, or third-party tags injected at runtime. Your RUM pipeline (lesson 2) should compute p75 LCP, INP, and CLS per route and device class daily, and alert on a sustained regression — not a single noisy day.

-- p75 LCP on mobile per route, per release, last 7 days
SELECT route, release,
       percentile_cont(0.75) WITHIN GROUP (ORDER BY value) AS p75_lcp,
       count(*) AS samples
FROM rum_metrics
WHERE name = 'LCP' AND device = 'mobile' AND ts > now() - interval '7 days'
GROUP BY route, release
HAVING count(*) > 500
ORDER BY route, release;

Because every beacon carries the release SHA, a regression points straight at a deploy window. Require a minimum sample size before alerting, or you'll page someone over twelve sessions from a bot.

Layer three: govern third parties

Third-party scripts bypass your bundler, your code review, and your CI. Treat them like dependencies: keep an inventory with an owner and a business justification for each, load them async or after interaction, review them quarterly, and measure their main-thread cost with the LoAF observer from lesson 8. A tag manager is a deployment pipeline that marketing controls; give it a budget too.

Make performance visible

  • Put p75 field vitals for the top routes on the same dashboard as error rate and conversion.
  • Add a "performance impact" line to the PR template for anything touching the initial route.
  • When a budget is hit, the conversation is "what do we remove to make room," not "raise the limit."

Field notes

  • Never gate PRs on the Lighthouse score. It's a weighted blend that changes between Lighthouse versions; assert on the specific metrics and sizes you care about.
  • Run lab CI against a production build with production compression and caching headers — a dev server lies about everything.
  • Expect CrUX to lag your fixes by up to 28 days. Use your own RUM for confirmation and CrUX for the external scorecard.
  • The best guardrail is cultural: the team that sees the field numbers every week keeps them green.