Skip to main content
Back to AI Commerce Lab
Performance·July 2026·9 min read

The Performance Work That Actually Moves Revenue

A Lighthouse score of 94 on your laptop describes what one page did once, on a fast machine, on a clean network, with no marketing tags loaded. Your p75 field data describes what the slowest quarter of your actual customers lived through. Only one of those two numbers has anything to do with revenue.

The pattern repeats on almost every commerce engagement we inherit. A performance sprint runs, the audit comes back green, the deck says "94 performance score", and 3 months later conversion has not moved.

The work was real. The measurement was not.

Performance is worth doing. It is worth doing in the order that matches how people shop, measured where they shop, and defended by something that blocks a merge rather than a slide that gets archived.

A lab score is a smoke test, not a measurement

Lighthouse simulates. It throttles CPU and network to a fixed profile, loads the page cold, and scores the result. That is useful as a regression smoke test and close to useless as a description of your customer base.

Field data comes from real sessions on real devices. The Chrome UX Report aggregates it, and the CrUX API reports each metric as a 28-day rolling p75. That number moves slowly, which is annoying inside a sprint and exactly what you want when deciding whether a change was real.

Run both. Lab catches a regression in CI within 10 minutes. Field confirms 4 weeks later that the regression you caught was one your customers were feeling.

Which metric maps to which shopping behavior

The 3 Core Web Vitals are not interchangeable, and the standard advice to improve all 3 spreads budget evenly across problems of very different size. Each metric degrades a different moment in the funnel.

MetricGood at p75Moment it governsCommerce symptom when it fails
LCP2.5s or lessFirst impression, paid landingAd traffic bouncing before the hero renders
INP200ms or lessEvery interaction after arrivalSize pickers, facet filters and the pay button feel dead
CLS0.1 or lessDense grids and product pagesMis-taps onto the wrong SKU or variant

Those thresholds come from the Web Vitals guidance, which also sets the assessment point at the 75th percentile of page loads, segmented across mobile and desktop.

LCP is a delivery problem before it is an image problem

Teams reach for image compression first because it is visible and easy to ship. Google's own breakdown splits LCP into 4 sub-parts and suggests a healthy profile spends roughly 40% in time to first byte and roughly 40% loading the LCP resource, with resource load delay and element render delay each under 10%.

Instrument those 4 sub-parts before you optimize a single image. If TTFB is 1.8s of a 3.4s LCP, the problem is origin response time, cache strategy, or a blocking personalization call, and every kilobyte shaved off the hero buys back nothing.

On an apparel storefront rebuild, LCP moved from 4.2s to 1.4s and mobile conversion lifted 25%. Image work was the last slice of that. Delivery and render path were the rest.

INP is where checkout quietly leaks

INP replaced FID because FID only looked at the first interaction, which is the easiest one a page ever handles. INP observes every click, tap and key press for the life of the page and reports the time from the interaction to the next fully presented frame. Good is 200ms or less, and anything past 500ms is rated poor.

That definition matters for commerce because the expensive interactions arrive late. Facet filters, size and color selection, quantity steppers and the pay button all run after the page has been open a while, after hydration, and after every third-party tag has settled.

Measure INP per route, never per site. A homepage at 90ms and a checkout at 640ms average into a green-looking origin while the only interaction that produces revenue is the broken one.

CLS is a merchandising problem wearing a CSS costume

CLS scores each unexpected shift as impact fraction multiplied by distance fraction, groups them into session windows, and reports the largest burst, per the web.dev definition. Shifts within 500ms of a user interaction are excluded, which is why opening a modal does not count against you.

In practice, commerce CLS comes from 4 places. Unsized product images in a grid, promo banners injected after hydration, a consent or region bar that pushes content down, and font swap on price labels.

All 4 are content decisions rather than framework decisions. Reserve layout space for the banner slot whether or not a banner ships that week, and merchandising can keep publishing without a performance review each time.

p75, 28 days, and why the median lies

p50 describes a customer on a recent phone with good signal. p75 describes the customer you are losing. Assessing at p75 is the whole reason field data is worth the instrumentation cost.

Collect it yourself alongside reading CrUX. The web-vitals library is roughly 3KB compressed and reports LCP, INP, CLS, FCP and TTFB from real sessions using the same definitions Chrome sends to CrUX.

Sending those beacons into your own analytics buys 2 things CrUX cannot give you: segmentation by route, device class and traffic source, and same-day feedback instead of a 28-day average. CrUX stays as the neutral arbiter you cannot fudge.

The metrics that are not Core Web Vitals but belong on the dashboard

  • TTFB per route, because origin health is the first 40% of LCP and the easiest thing to regress with a new middleware.
  • Long tasks over 50ms in the first 10 seconds, because they predict INP failures before INP itself moves.
  • Third-party script weight as a share of total bytes, tracked over time. Tag managers accumulate and nobody ever removes a tag.
  • Hydration cost on the product page, measured as time from first paint to first successful interaction.
  • Error rate bucketed by latency. Slow sessions and failed sessions overlap far more than most dashboards show.

The budget that fails the build

A launch-week audit is a photograph. A performance budget is a fence. web.dev groups budgets into 3 kinds: quantity-based limits such as page weight and request count, milestone timings such as FCP, and rule-based scores from a tool like Lighthouse.

Quantity budgets are the ones worth enforcing hardest because they are deterministic. A script bundle either exceeds 180KB compressed or it does not, and no amount of CI variance changes that answer.

Lighthouse CI runs the audit on every pull request, stores results, and asserts against them. Point it at the routes that carry revenue rather than at the homepage alone.

// lighthouserc.js - assertions that fail the build, not a dashboard
module.exports = {
  ci: {
    collect: {
      url: [
        'https://staging.example.com/',
        'https://staging.example.com/c/outerwear',
        'https://staging.example.com/p/example-sku',
        'https://staging.example.com/checkout',
      ],
      numberOfRuns: 5,
    },
    assert: {
      assertions: {
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
        'total-blocking-time': ['error', { maxNumericValue: 300 }],
        'resource-summary:script:size': ['error', { maxNumericValue: 184320 }],
        'resource-summary:third-party:count': ['warn', { maxNumericValue: 12 }],
      },
    },
  },
};

Two details make this survive contact with a real team. Run each URL 5 times so run-to-run variance does not produce flaky failures, and start every new assertion at warn for 2 sprints before promoting it to error.

A budget nobody can pass gets disabled within a month. Set the opening threshold at today's measured number plus a small margin, then ratchet it down as work lands.

Third parties are the budget you never agreed to

On most commerce sites we inherit, third-party tags outweigh first-party application code by script bytes. Nobody decided that. It accumulated one campaign at a time, each addition individually defensible.

Treat the tag list as a portfolio with an owner and a renewal date. Every tag gets a named business owner, a stated purpose, and a quarterly review where the default answer is removal unless someone argues for it.

Technically, the levers are narrow and well understood. Load non-critical tags after the load event, put anything that only affects analytics behind an idle callback, and refuse synchronous third-party scripts in the document head without a written exception.

The hard part is organizational. A performance budget that only binds engineering while marketing adds tags freely is a budget that engineering will lose, quarter after quarter, and the p75 will tell that story clearly.

The order we work in

  1. Instrument first. Field beacons segmented by route and device class, plus a CrUX baseline. No optimization until you can see the p75 you intend to move.
  2. Fix the highest-traffic revenue route, not the worst route. A product template serving 40% of sessions beats a broken account page every time.
  3. Break LCP into its 4 sub-parts before choosing a fix. Server, then render path, then bytes, in that order.
  4. Audit third parties as a portfolio rather than one at a time. Ask which tag owner would notice if it disappeared for a week.
  5. Land the budget in CI before the sprint closes. Gains without a gate decay within 2 releases.

When the boring option wins

Most commerce performance problems are not solved by a framework migration. They are solved by caching the category response, deleting 6 marketing tags, sizing images properly, and moving personalization from a blocking call to a post-render patch.

Migrations are sometimes worth it. On one multi-generation modernization, moving a JSP and CMS-driven storefront to React took PageSpeed from 60 to 90+ and P95 page load from 6s to under 2s. That program ran for years and was justified by far more than page speed.

If the rationale for a rewrite is a performance number, get the number with the boring work first. If the boring work gets you there, you saved a year. If it does not, you now hold a measured argument for the rewrite instead of a hopeful one.

FAQ

Should we chase a Lighthouse score of 100?

No. Past roughly 90, the remaining points come from lab-only artifacts that no customer experiences. Spend that budget on p75 field data instead.

How long before a fix shows up in Core Web Vitals?

CrUX publishes a 28-day rolling average, so a change landing today is fully reflected around 28 days later and partially visible sooner. Your own beacons show it the same day.

Does INP fully replace FID?

Yes. INP measures every interaction across the page lifetime rather than only the first, which is why plenty of sites that passed FID comfortably now fail INP.

What is a reasonable script budget for a product page?

Set it from your current number rather than from a blog post. Measure today's compressed script weight, add 5%, make that the error threshold, and ratchet down each quarter.

Do performance budgets slow a team down?

They slow down the specific pull requests that would have cost you revenue. Everything else merges without comment, which is the point.

We are already headless. Are we fine?

Headless moves the bottleneck rather than removing it. Most headless commerce sites we inherit have an excellent TTFB and a poor INP, because the work moved to the client.

References

From the Destm engineering archive. For current work on this topic, start at Solutions or the blog.