Web Solutions

How to Make Your Website Fast: A Core Web Vitals Guide

A practical Core Web Vitals guide: current LCP, INP and CLS thresholds, and how to fix images, fonts, JavaScript, caching and CDNs, measured with real data.

GPTLabAI team 7 min read

To make your website fast in a way Google and your visitors both notice, focus on the three Core Web Vitals: how quickly the main content appears (LCP), how quickly the page responds to input (INP), and how stable the layout is while it loads (CLS). This Core Web Vitals guide explains the current thresholds, the fixes that move each metric, and how to measure with real-user data instead of guessing.

The Core Web Vitals and their thresholds

As of September 2026, the Core Web Vitals are the same three metrics Google has used since INP replaced First Input Delay in March 2024. Each is judged at the 75th percentile of real page loads, separately for mobile and desktop.

Metric What it measures Good Needs improvement Poor
LCP (Largest Contentful Paint) Time until the largest image or text block is visible ≤ 2.5 s 2.5–4 s > 4 s
INP (Interaction to Next Paint) Delay between a click, tap or key press and the next visual update ≤ 200 ms 200–500 ms > 500 ms
CLS (Cumulative Layout Shift) How much visible content jumps around unexpectedly ≤ 0.1 0.1–0.25 > 0.25

“75th percentile” matters: a page passes only if at least three quarters of real visits hit the “good” value. A fast result on your office Wi-Fi does not count if most customers visit on mid-range phones over mobile data.

Measure first: field data vs lab data

There are two kinds of performance data, and you need both.

  • Field data comes from real Chrome users through the Chrome User Experience Report (CrUX). It is what Google uses to assess your pages. It covers a rolling 28-day window, so fixes take a few weeks to show up fully.
  • Lab data comes from a single simulated load, for example in Lighthouse. It is reproducible and great for debugging, but it is not what your users actually experienced.

Tools we use:

  • PageSpeed Insights shows CrUX field data at the top (if your site has enough traffic) and a Lighthouse lab run below. Start here.
  • Google Search Console has a Core Web Vitals report that groups URLs with similar problems across the whole site.
  • Chrome DevTools Performance panel for tracing exactly what the browser does during load and interactions.
  • The web-vitals JavaScript library to collect LCP, INP and CLS from your own users and send them to your analytics. Useful when your site is too small to appear in CrUX.

One caveat: a Lighthouse run cannot measure INP, because nobody interacts with the page. It reports Total Blocking Time instead, which is a useful proxy for main-thread congestion. To see real INP, you need field data.

Fixing LCP: get the main content on screen sooner

LCP is usually a hero image, a large heading, or a banner. Slow LCP almost always comes from one of four places: slow server response, a late-discovered resource, a heavy resource, or render-blocking CSS and JavaScript.

Speed up the server response

  • Cache full HTML pages where you can. Static pages should come straight from a CDN or cache, not from PHP or Node on every request.
  • For dynamic apps, find slow database queries and add indexes. A 1.5-second server response leaves almost no budget for anything else.
  • Serve over HTTP/2 or HTTP/3 and enable Brotli or gzip compression.

Make the LCP image fast

  • Serve modern formats (AVIF or WebP) at the size actually displayed, using srcset and sizes.
  • Never lazy-load the LCP image. Lazy-load images below the fold instead.
  • Tell the browser it is important with fetchpriority="high".
<img
  src="/img/hero-1200.avif"
  srcset="/img/hero-800.avif 800w, /img/hero-1200.avif 1200w, /img/hero-1600.avif 1600w"
  sizes="(max-width: 800px) 100vw, 1200px"
  width="1200" height="630"
  alt="Team reviewing a dashboard"
  fetchpriority="high">

If the LCP image is a CSS background, the browser discovers it late. Either switch to an <img> element or add <link rel="preload" as="image" ...> in the <head>.

Remove render-blocking resources

  • Inline the small amount of CSS needed for above-the-fold content, and load the rest normally.
  • Add defer to scripts that are not needed for the first paint.
  • Move third-party tags (chat widgets, heatmaps, A/B testing) off the critical path, or remove the ones nobody looks at.

Fixing INP: keep the main thread free

INP measures responsiveness across the whole visit, not just the first click. Poor INP is almost always a JavaScript problem: long tasks that block the main thread so the browser cannot respond to input.

  • Ship less JavaScript. Audit your bundles. Remove unused libraries, replace heavy ones with lighter alternatives, and split code by route.
  • Break up long tasks. Work that takes more than about 50 ms should be split so the browser can handle input in between. Use scheduler.yield() where the browser supports it, and fall back to setTimeout where it does not.
  • Do less on each interaction. Update the UI first (show the spinner, open the menu), then do the expensive work.
  • Avoid huge DOM updates. Rendering thousands of rows at once is slow. Paginate or virtualise long lists.
  • Watch third-party scripts. Tag managers and analytics can dominate the main thread. Load them after the page is interactive.
  • Consider islands or partial hydration. Frameworks such as Astro only hydrate the interactive parts of a page, which keeps the main thread quieter by default.
async function handleFilterChange(items) {
  showLoadingState();          // give instant visual feedback
  await yieldToMain();         // let the browser paint
  const result = expensiveFilter(items);
  render(result);
}

function yieldToMain() {
  if (globalThis.scheduler?.yield) return scheduler.yield();
  return new Promise((resolve) => setTimeout(resolve, 0));
}

The web.dev guide to optimising INP goes further into diagnosing which interactions are slow.

Fixing CLS: reserve space for everything

Layout shifts happen when something appears or resizes after the content around it has already rendered.

  • Always set width and height on images and videos (or use CSS aspect-ratio) so the browser reserves the right space.
  • Reserve space for ads, embeds and banners. A cookie banner that pushes content down is a classic CLS source; overlay it instead.
  • Do not insert content above existing content unless it is in response to a user action.
  • Animate with transform and opacity, not properties that change layout such as top or height.
  • Handle web fonts carefully (see below), because a font swap can change text size and reflow the page.

Fonts

  • Self-host fonts where licensing allows, and serve them as WOFF2.
  • Load only the weights and character subsets you actually use.
  • Use font-display: swap (or optional for non-critical fonts) so text is visible immediately.
  • Preload the one or two fonts used above the fold.
  • Reduce the swap shift with fallback metric overrides (size-adjust, ascent-override) so the fallback font takes up the same space.
@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-var.woff2") format("woff2");
  font-display: swap;
  font-weight: 100 900;
}

Caching and CDNs

Caching is often the cheapest performance win of all.

  • Fingerprinted static assets (app.3f9a1c.js, logo.8b2e.svg) can be cached for a year with Cache-Control: public, max-age=31536000, immutable.
  • HTML should be cached briefly or revalidated, so updates show up quickly.
  • A CDN serves files from locations close to your users, which cuts latency for everyone far from your origin server. For static sites, the whole site can live on the CDN.
  • Back/forward cache (bfcache): avoid unload event handlers and Cache-Control: no-store on normal pages, so the browser can restore pages instantly when users press Back.

On Apache hosting, a simple starting point:

<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType image/avif "access plus 1 year"
  ExpiresByType image/webp "access plus 1 year"
  ExpiresByType font/woff2 "access plus 1 year"
  ExpiresByType text/css "access plus 1 year"
  ExpiresByType application/javascript "access plus 1 year"
  ExpiresByType text/html "access plus 0 seconds"
</IfModule>

Only set long lifetimes on files whose names change when their content changes; otherwise users will keep seeing old versions.

A practical order of work

If you are starting from a slow site, this order usually gives the most improvement for the least effort:

  1. Check PageSpeed Insights and Search Console to see which metric fails and on which page types.
  2. Fix server response time and caching.
  3. Optimise and correctly prioritise the LCP image.
  4. Add image dimensions and reserve space for dynamic elements (CLS).
  5. Remove or defer third-party scripts.
  6. Reduce and split JavaScript, and break up long tasks (INP).
  7. Add field monitoring with the web-vitals library so regressions are caught early.

Key takeaways

  • Know the targets: LCP ≤ 2.5 s, INP ≤ 200 ms, CLS ≤ 0.1, at the 75th percentile
  • Judge success by field data (CrUX), debug with lab data (Lighthouse)
  • LCP image: modern format, right size, not lazy-loaded, fetchpriority="high"
  • Less JavaScript, fewer third-party tags, long tasks broken up
  • Width and height on every image and embed
  • WOFF2 fonts, font-display, only the weights you use
  • Long cache lifetimes for fingerprinted assets, a CDN in front
  • Real-user monitoring so you notice when things get slower

Speed work pays off twice: better rankings and better conversion. If your site is failing Core Web Vitals and you are not sure where the time is going, our web solutions team can profile it, fix the biggest bottlenecks and set up monitoring. Send us the URL and we will start from the data.

Have a project in mind? Let’s talk.

Whether you run a business or a research group, tell us what you need built, fixed or evaluated. You get a free consultation and a clear written estimate — no obligation.

  • Free consultation
  • Written scope and estimate
  • We reply within one working day
Contact us