Plain source, for humans in a hurry and machines in general.
Every performance audit produces the same document: forty findings, no order, and a note that a rewrite would help. That is not useful to a team with a roadmap. What follows is the order we work in, which is roughly descending leverage per unit of risk.
Largest Contentful Paint: what is the browser waiting for?
LCP measures when the biggest thing in the viewport finishes rendering. It is almost never slow because the server is slow. It is slow because the browser could not start work on the important thing early enough.
Render-blocking requests first. Every stylesheet and every synchronous script in the head must be fetched, parsed and executed before anything paints. Count them on your slowest page. Inline what the first viewport genuinely needs, defer the rest, and be suspicious of any CSS framework’s full build shipping to a page that uses 5% of it.
Fonts, which are the most common single culprit. A web font discovered inside a CSS file is two round trips deep before the browser knows it exists. Self-host, preload the one or two weights actually used, and set font-display: swap so text paints immediately in a fallback. That alone frequently moves LCP by several hundred milliseconds.
Then the hero image. Correct dimensions rather than a 3000px original scaled down, a modern format, fetchpriority="high" on the one image that is the LCP element, and lazy loading on everything below the fold but explicitly not on that one. Lazy-loading the hero is a self-inflicted wound and a surprisingly frequent one.
Interaction to Next Paint: stop blocking the main thread
INP measures the delay between a user interacting and the screen updating. The cause is nearly always JavaScript occupying the main thread when the tap arrives.
Start by auditing what you ship. Third-party tags (analytics, tag managers, chat widgets, session recording) are usually the largest contributors and the easiest to defer, gate behind consent, or remove outright. Each one is a negotiation between a marketing requirement and a measurable cost, and the cost is now measurable, which helps.
For your own code, look for long tasks in a profile and break them up. Yield to the browser between chunks of work. Move genuinely heavy computation to a worker. Avoid layout thrashing: reading a geometry property after writing one forces a synchronous reflow, and doing it in a loop is how a scroll handler becomes a stutter.
Cumulative Layout Shift: reserve the space
CLS is the most fixable of the three, because the cause is always the same: something arrived after layout and pushed content around.
Set width and height on every image and video, so the browser reserves the box before the bytes arrive. Give ad slots, embeds and iframes a fixed container. Never insert a banner above existing content after load. Put it in the markup from the start, or overlay it. Use font-display: optional or a metrics-matched fallback if font swapping visibly reflows your headings.
What this looked like on this site
This site scores 100 on the homepage, on a stack chosen partly to make that achievable. Concretely: fonts self-hosted and preloaded; no third-party scripts at all, which also means no cookie banner; the mock UI panels are HTML and CSS rather than images, so they cost nothing to download and stay sharp; and the marketing pages ship about 19 KB of gzipped JavaScript because they load Alpine alone, with the component framework reserved for the one page with a form on it.
That last decision is the one worth generalising. Most pages on most sites do not need the JavaScript they load. Deciding per page rather than per site is a build-time concern with a real payoff.
The order, condensed
- Remove or defer render-blocking CSS and JavaScript.
- Self-host and preload fonts;
font-display: swap. - Size, format and prioritise the LCP image correctly.
- Defer, gate or delete third-party scripts.
- Set explicit dimensions on all media and embeds.
- Profile for long tasks and break them up.
- Measure field data, not just lab scores.
Steps one to five are usually a few days on an existing codebase and usually enough. Step six is where the remaining work lives, and step seven is what tells you whether any of it reached your actual users.
Questions people also ask
Do Core Web Vitals actually affect rankings?
Lab scores are green but field data is amber. Which counts?
Is a single-page application inherently worse for this?
How much can we improve without a rewrite?
Who wrote this
Alexander (Sander) van Hooff
Alexander (Sander) van Hooff has been building web applications since 2015 and now spends most of his time getting AI features into products that already have users. Vuewer is his studio, run from the Alicante region of Spain for clients across Europe.
Work with VuewerRelated reading
Multilingual Laravel: translated routes done properly
Why /nl/pricing loses to /nl/tarieven, how to build translated slugs in Laravel, and the hreflang mistakes that quietly cost you the other languages.
Why we still choose Laravel in 2026
An honest argument for a ten-year-old framework: what Laravel does better than the alternatives, what it does worse, and the cases where we would pick something else.
SEO, AEO and GEO: what actually changed
Three acronyms, one shift: search engines now answer instead of link. What that changes about how pages should be written, and what it does not change at all.