# Getting Core Web Vitals green on a real application

> Fix the three metrics in order of leverage: cut render-blocking requests and serve the hero image properly for LCP, break up long JavaScript tasks for INP, and reserve space for anything that loads late for CLS. On most existing applications, four changes (font loading, image sizing, deferred third-party scripts and explicit dimensions) get you from amber to green without touching architecture.

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

1. Remove or defer render-blocking CSS and JavaScript.
2. Self-host and preload fonts; `font-display: swap`.
3. Size, format and prioritise the LCP image correctly.
4. Defer, gate or delete third-party scripts.
5. Set explicit dimensions on all media and embeds.
6. Profile for long tasks and break them up.
7. 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.

---

Published by Vuewer. AI features and web applications, shipped to production.
Canonical version: https://vuewer.com/blog/core-web-vitals-on-a-real-application