Core Web Vitals 2026: a practical implementation guide
Core Web Vitals have been a ranking factor for almost five years now and a UX baseline even longer. Yet most sites we audit still fail at least one of the three. Not because the targets are hard to hit — they’re not — but because the work is unglamorous and gets de-prioritized below shinier projects.
Here’s what the current metrics measure, where typical sites fail, and the implementation patterns that consistently pass.
The three metrics in plain language
LCP — Largest Contentful Paint
How fast the biggest visible element renders. For most marketing sites, this is the hero image or the H1 paragraph. Target: under 2.5 seconds. Excellent: under 1.5 seconds.
CLS — Cumulative Layout Shift
How much things move around as the page loads. Buttons that shift right as ads load. Content that jumps when fonts swap. Target: under 0.1. Excellent: under 0.05.
INP — Interaction to Next Paint
How long it takes the page to visibly respond when a user taps or clicks something. Replaced FID in 2024. Target: under 200ms. Excellent: under 100ms.
These three measure the user-perceived performance of your site. Not synthetic lab tests — real users on real devices on real connections.
Where typical sites lose points
1. Heavy hero images, served at the wrong size
A 3MB hero image at 2400×1600 served to a phone is the single most common LCP killer we see. Fix: serve the right size for the viewport, in AVIF or WebP, with proper srcset and sizes attributes. Add fetchpriority="high" to your hero image so it doesn’t wait behind less important resources.
2. Custom fonts loaded without fallback
Fonts loaded from fonts.googleapis.com without font-display: swap cause your text to be invisible until the font loads. That hurts LCP if the largest element is text. Fix: always use font-display: swap. Better: self-host fonts and preload the one used for above-the-fold text.
3. JavaScript blocking the main thread
Big JS bundles parsing on the main thread before anything renders. Common with traditional React SPAs, certain WordPress themes, and most tag-manager-heavy setups. Fix: defer non-critical scripts, code-split aggressively, and consider whether you need a SPA at all (most marketing sites don’t).
4. Layout shift from ads, embeds, and lazy images
Lazy-loaded images without width and height attributes cause shift when they pop in. Ads inserted after page load push everything down. Fix: always specify image dimensions, reserve space for ads and embeds via CSS aspect-ratio, never inject content above existing content.
5. Hydration cost on interactive sites
Interactive sites built with full SPA hydration pay a one-time INP cost on initial load that’s often hundreds of ms. Fix: use partial or selective hydration. Astro’s islands architecture is built for this. Next.js’s Server Components solve a related problem.
The three thresholds, visualized
Implementation patterns that pass
Hero image, done right
<img
src="/hero.avif"
srcset="
/hero-640.avif 640w,
/hero-1024.avif 1024w,
/hero-1920.avif 1920w
"
sizes="(max-width: 768px) 100vw, 50vw"
alt="Product screenshot"
width="1920"
height="1080"
fetchpriority="high"
decoding="async"
/>
The combination of fetchpriority="high", explicit dimensions, modern formats, and proper srcset will get most sites under a 1.5s LCP without any other changes.
Font loading, done right
<link
rel="preload"
href="/fonts/inter-var.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<style>
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-var.woff2') format('woff2-variations');
font-weight: 100 900;
font-display: swap;
}
</style>
Preload the font file, use font-display: swap, and self-host so you control the cache headers. Skip Google Fonts unless you have a specific reason.
Reserving space for media
.media-frame {
aspect-ratio: 16 / 9;
background: var(--bg-soft);
}
.media-frame > img,
.media-frame > video,
.media-frame > iframe {
width: 100%;
height: 100%;
object-fit: cover;
}
Combined with width/height attributes on images, this eliminates the most common source of layout shift.
Defer the JavaScript you don’t need on load
<!-- Critical inline -->
<script>
// Only what must run before paint
</script>
<!-- Defer everything else -->
<script src="/app.js" defer></script>
<!-- Analytics & tag managers -->
<script src="https://plausible.io/js/script.js" defer data-domain="adfirm.net"></script>
defer is your friend. async is for scripts that don’t depend on the DOM. Inline <script> blocks should be tiny and only run code that must execute before paint.
INP — what to actually change
Most INP problems come from:
- Long event handlers. Break work into smaller tasks. Use
requestIdleCallbackfor non-critical work. - Hydration. Use island architecture for marketing sites. Use React Server Components or Qwik resumability for apps.
- Third-party scripts. Audit them. The single biggest INP improvement we’ve made for a client was removing four tag-manager scripts they weren’t actively using.
Measuring honestly
Lighthouse is a lab test. It’s useful but optimistic. Real users are slower than Lighthouse predicts. Use both:
- Lighthouse — for catching regressions in CI. Run on every PR.
- Chrome User Experience Report (CrUX) — for real-world data. Available via PageSpeed Insights or the CrUX API.
- Web Vitals JS library — to capture your own users’ metrics and ship them to analytics.
If your CrUX numbers and your Lighthouse numbers diverge by a lot, trust CrUX. That’s what Google ranks on.
The 80/20 priority list
If you’ve got an hour and want to improve a site’s Core Web Vitals score, do these in order:
- Add
fetchpriority="high"to your hero image - Specify
widthandheighton every image and video - Convert hero images to AVIF or WebP
- Defer all non-critical scripts
- Audit third-party scripts and kill anything you don’t actively use
- Self-host fonts with
font-display: swap - Add
aspect-ratioCSS to media containers
That sequence alone usually moves a failing site to passing.
The hidden ROI
Core Web Vitals improvements move three things you’d care about even if Google didn’t:
- Bounce rate drops measurably below 2-second LCP
- Conversion rate improves on the order of 1-3% per 100ms of LCP improvement
- Crawl budget stretches further when pages load fast
Treat performance as a conversion optimization, not just an SEO optimization. The math gets a lot more interesting.