Skip to content
(01) // PERFORMANCE

Core Web Vitals are an architecture problem

5 MIN READPERFORMANCEUPDATED 31 AUG 2026

Most failing Core Web Vitals scores are not caused by unoptimised images, but by shipping a framework to render text. Here is the order to fix it in.

Teams usually try to fix a failing performance score from the bottom up: compress the images, defer a script, add a cache header. Those help. They rarely move a mobile score from the fifties into the nineties, because the score is mostly reporting an architectural decision — that the page is assembled in the browser rather than delivered as a document.

The order that actually works

  1. Decide what must be interactive. Usually a fraction of the page. Everything else can be static HTML.
  2. Ship the document first. Text and layout should be readable and stable before any script executes.
  3. Isolate the interactive parts. Each one loads on its own terms — on view, on interaction, on idle.
  4. Then optimise images, fonts and headers. This is the cheap part, and it only shows up once the main-thread work is gone.

Doing these in the wrong order is why so much performance work feels unrewarded. Compressing images on a page that ships 400KB of JavaScript before it renders text moves a metric by a rounding error, and the team concludes that performance work does not pay.

Why images are rarely the real problem

An unoptimised hero image hurts one metric on one page. A framework bundle that has to parse, execute and hydrate before text appears hurts every metric on every page, and it hurts them worst on the devices most of your users actually hold.

The asymmetry is in the CPU, not the network. A 300KB image and 300KB of JavaScript are the same download. The image is then decoded on a dedicated path; the JavaScript has to be parsed, compiled and executed on the main thread — the same thread that has to respond when someone taps. On a mid-range Android phone that work can take several seconds, and it is invisible from a developer’s laptop on office wi-fi.

The three metrics, and what each one is really telling you

LCP (Largest Contentful Paint) is usually a story about what has to happen before your biggest element can render. If your hero text is inside a component that hydrates, LCP is gated on your bundle, not on your image. Fix the dependency chain before you fix the asset.

CLS (Cumulative Layout Shift) is almost always unreserved space: images without dimensions, web fonts swapping to a different metric, an ad or banner injected above content after paint. It is the cheapest of the three to fix and the most embarrassing to leave broken, because it is entirely a discipline problem.

INP (Interaction to Next Paint) replaced First Input Delay in 2024 and is much harder to game. FID measured how long the browser took to start handling an interaction; INP measures the whole path to the next paint, across the whole session. A page can pass FID and fail INP badly, because the expensive part was never the delay before the handler — it was the 300ms of re-rendering the handler triggered.

INP is the metric that punishes “we will fix performance later” hardest, because it is a function of how much work your components do per interaction. That is architecture, and it is not tunable with a build flag.

The trade-offs nobody mentions

Static-first is not free, and pretending otherwise is how teams get burned.

Build times grow with content. A site with 50 pages builds instantly. One with 50,000 needs incremental builds or on-demand rendering, and you now have a build pipeline to operate. For a marketing site or a publication this is a good trade. For a highly personalised dashboard, it is the wrong shape entirely — that page is supposed to be assembled per user, and the honest fix there is making the shell fast and the data fetch parallel, not going static.

Islands cost coordination. Independently hydrating components cannot casually share client state. If three islands genuinely need the same store, you either lift them into one island, or you accept a small amount of cross-island plumbing. Both are fine; discovering the requirement after the fact is not.

Fewer abstractions means more explicit work. Without a framework mediating everything, you write the fetch, the loading state and the error state yourself. That is a real cost in developer time, bought back in runtime cost. Whether that trade is worth it depends on whether your users are on your laptop or on a bus.

Third-party scripts can undo all of it. A perfectly architected page with a tag manager, a chat widget and two analytics scripts is a slow page. This is usually a political problem rather than a technical one, and it is worth having the argument with numbers before you spend a sprint on your own code.

What to measure

Field data over lab data, and the 75th percentile rather than the average. A median that looks fine while the 75th percentile is failing means a segment of your users — usually the ones on mid-range Android — are having a materially worse experience than your team is.

Lab tools are for diagnosis, not for grading. Use a throttled local run to find why something is slow, then use field data to decide whether it matters. A Lighthouse score run from a developer machine with extensions installed is noise, and treating it as a target produces work aimed at the tool rather than the user.

Make it a budget, not a project

The reason performance regresses is that nothing fails when it does. Give each route a JavaScript budget and an LCP target, assert them in CI, and treat a breach the way you treat a failing test — because that is what it is. A budget turns an open-ended quality goal into a binary check somebody has to consciously override, which is the only mechanism that survives a deadline.

This is how we scope web engagements, including the ones with a real-time 3D layer, where the scene gets its own weight ceiling alongside the bundle. On ecommerce work the argument makes itself: the budget is a conversion decision wearing a technical costume.

Sources and further reading

performancecore web vitalsarchitecture
START A
PROJECT
EMAIL CHANNELHELLO@BEECODEX.COM
SYSTEMS ONLINE / TAKING NEW PROJECTS