Core Web Vitals are a peculiar set of metrics; easy to chase and notoriously difficult to actually move. Across the eighteen months that followed Google’s full rollout of the metrics as a ranking signal, I led performance work on a portfolio of more than one hundred retail websites at Praxis Digital 3D. The brief, repeated by anxious marketing directors with predictable regularity, was always the same — “we need to be in the green on Core Web Vitals before the next reporting cycle.” The reality, as is often the case in performance engineering, was rather more nuanced than the brief suggested.
What follows is a practitioner’s account of what genuinely moved the metrics during that work, what cost considerable engineering effort and produced no measurable improvement, and what fell somewhere in between — improvements that were real but small enough that the time spent on them is difficult to justify in retrospect. There is no shortage of blog posts on Core Web Vitals from people who have read the documentation; this is intended to be a contribution from someone who has, with varying degrees of success, attempted to ship the recommendations to a great many production sites.
The cheap wins are the only ones that scale
The first observation is one that every performance engineer learns within their first month of doing this work, but which appears to remain non-obvious to most teams: the highest-leverage performance interventions are also the cheapest to implement. Lazy-loading below-the-fold imagery, deferring third-party scripts, and removing render-blocking webfonts together produced the majority of LCP improvements observed across the portfolio. None of these interventions are intellectually exciting; none of them require deep architectural changes; all of them are achievable in roughly thirty minutes per site.
The reason these wins compound across a portfolio of one hundred sites is simple multiplication. A thirty-minute fix that improves LCP by half a second on each site, applied across a hundred sites, results in a fifty-hour engagement that produces a half-second LCP improvement across the entire commercial portfolio. The same engineering hours, spent on a single architecturally clever fix on one site, would yield a much larger improvement on that one site and nothing for the other ninety-nine. This is the multiplication that the wider performance discourse, focused as it is on demonstrations of single-site optimisations, persistently undervalues.
Lazy loading deserves a particular note. Native browser support for loading="lazy" is now sufficiently mature that a site that does not use it on its product imagery is leaving an entirely free improvement on the table. The implementation is a single attribute, the rendering implications are well-documented, and the only edge case worth mentioning is that the very first image on the page — the hero or the first product card — should not be lazy-loaded; indeed, on certain sites, this image benefits from explicit fetchpriority="high" to bias the browser’s resource scheduler towards loading it sooner.
Third-party script deferral is similarly cheap and similarly underused. The typical retail site, in my observation, loads between eight and fifteen third-party scripts; the typical configuration of these scripts is to be loaded synchronously in the document head, where they block the rendering of the rest of the page. Moving these scripts to the document footer, or applying the defer attribute, costs nothing and produces an immediate improvement in both LCP and INP. The principal objection raised by marketing teams is that doing so might cause analytics to miss a small fraction of pageviews from users who navigate away within the first second; this objection, while technically correct, is in practice an objection to losing data on the very users whose dissatisfaction the performance work is intended to reduce.
The expensive wins are sometimes worth it
Beyond the cheap wins lies a class of interventions that are real, measurable improvements but which require considerable engineering effort to implement. The most consequential of these, in my experience, is the conversion of product listing pages from client-side rendering to server-side rendering. On the sites where this work was completed, average LCP improvements of approximately 1.4 seconds were observed; the principal cost was the engineering time required to extract the listing logic from the JavaScript runtime and re-implement it within the server-side template layer.
Whether this work is worth doing depends almost entirely on the architectural starting point. For a site already built on a server-rendered framework — Umbraco, WordPress, Shopify Plus with theme rendering — the work is incremental and the payoff is high. For a site built on a client-rendered SPA architecture, with no existing SSR layer, the work approaches a complete rebuild of the listings template, and the cost-benefit calculation becomes considerably less favourable. (For more on this, see Umbraco + .NET in 2026, which addresses the broader question of when traditional CMSes still pay their way.)
A second class of expensive intervention concerns the layout-shift score, CLS. The most common cause of poor CLS on retail sites is the late-arriving advertisement, the late-arriving cookie banner, or the late-arriving review widget — each of which inserts itself into the page after the initial render and pushes existing content out of position. The cheapest mitigation is to reserve vertical space for these elements via fixed-height containers; the more expensive mitigation is to load the elements eagerly enough that their final state is achieved before the user has begun to interact with the page. Reserving space costs nothing and produces a meaningful improvement; eager loading often produces a marginally larger improvement at considerable cost in initial bundle size.
The theatre, which everyone secretly knew was theatre
There exists a third category of performance work which, despite the considerable engineering effort it requires, produces no measurable improvement on any metric the user might plausibly notice. The most popular form of this theatre, during the period of the work described, was the practice of preloading a great many resources via <link rel="preload"> declarations in the document head; the conventional wisdom held that doing so would bias the browser’s resource scheduler in favour of the most important assets, which would in turn produce LCP improvements.
In practice, preloading more than two or three resources tends to produce no improvement and, on connections constrained by bandwidth, can produce an active regression as the browser is instructed to fetch resources before they are actually needed. We measured the effect across a representative sample of the portfolio and found that the median improvement of preloading more than three resources was zero; the worst-case regression observed was approximately two hundred milliseconds on a moderately constrained mobile connection.
Critical-CSS inlining produced a similarly disappointing return. The conventional wisdom — that the first paint of the page can be accelerated by inlining the CSS required for the above-the-fold content directly into the document head — is technically correct; the difficulty is that the engineering work required to identify, extract and maintain the critical CSS is considerable, and that the resulting improvement on a typical retail site is, in our measurements, approximately fifty milliseconds. There exist circumstances in which fifty milliseconds is meaningful; there exist a great many more circumstances in which it is not, and the engineering cost of maintaining critical-CSS extraction over time is rarely justified by the improvement.
Tree-shaking the JavaScript bundle to within a kilobyte of its theoretical minimum is the third member of the performance-theatre triad. It is satisfying work; it produces visible decreases in bundle size in the build output; it does not, in my experience, produce measurable improvements in any user-facing metric beyond the bundle-size readings on Lighthouse itself. The reason is straightforward: the difference between a 180-kilobyte bundle and a 165-kilobyte bundle, on a connection capable of delivering several megabytes per second, is well within the noise floor of the rest of the page-loading process. Spend the engineering time elsewhere.
Two patterns the documentation does not mention
Two observations emerged during the work that I have not seen written about elsewhere, and which I will offer here for what they are worth.
The first concerns the relationship between Core Web Vitals scores and the perceived speed of the site as reported by users. The two are correlated, but they are not identical; sites with substantially better LCP scores were reported by user-research participants as feeling faster only when the LCP improvement was greater than approximately eight hundred milliseconds. Below that threshold, the improvement existed in the metric but did not exist in the user’s experience; the engineering work that produced these sub-threshold improvements is therefore difficult to justify on user-experience grounds, even when it is justifiable on SEO grounds.
The second concerns the variability of the metrics themselves. A single site, measured on the same day, on the same connection, in the same browser, frequently produced LCP readings that varied by several hundred milliseconds between successive page loads. This variability is partly the result of network conditions, partly the result of which third-party resources happen to be served from cached versus uncached origins, and partly the result of the well-documented imperfections of the instrumentation itself. The practical implication is that any individual LCP reading should be treated with considerable scepticism; meaningful conclusions can only be drawn from a distribution of readings collected over time. (Google’s own LCP documentation acknowledges as much, though the acknowledgement is somewhat understated.)
On the budget question
Most of the conversations I had with marketing directors during this period reduced, in the end, to a budget question. Performance work is engineering work; engineering hours are finite; the question is always which engineering hours produce the largest improvement in the metrics that matter. The answer that emerged from the work, repeated and refined across more than a hundred sites, is that the cheap wins should be applied universally before any expensive wins are even considered, that the expensive wins should be applied selectively based on the architectural starting point of the site in question, and that the theatre should be politely declined regardless of the budget available.
It was somewhat expected, going into the work, that the largest improvements would come from the most expensive interventions; what is unexpected, in retrospect, is the consistency with which this expectation proved wrong. There is, in performance engineering as in much of digital commerce, no substitute for actually measuring the result. (For the closely related question of how this thinking applies to demographic performance differences, see Why Gen Z bounces off your filters and millennials don’t.)