Skip to content

Expert · Fast, accessible, findable and safe: quality for real sites · topic 46 of 64 · 35 min

Performance and Core Web Vitals

Explanation

A fast page keeps visitors; a slow one loses them, especially on budget phones and busy mobile data. Google measures real user experience with three Core Web Vitals: LCP (how fast the main content shows, good at 2.5 s or less), INP (how fast the page reacts to taps, good at 200 ms or less) and CLS (how much the layout jumps, good at 0.1 or less). You measure with Lighthouse and PageSpeed Insights, then fix images, scripts and fonts.

Many visitors in India use a budget Android phone on a crowded 4G network. A page that feels fine on your laptop and office Wi-Fi can take 8 seconds on that phone. People leave slow pages, shops lose orders, and Google uses page experience as one of many signals in search. Performance is not a luxury; it decides who can use your site.

Think of it like this: Think of a restaurant. LCP is how long until your main thali arrives. INP is how fast the waiter responds when you wave for one more roti. CLS is whether the table shakes and your glass slides while you eat. A good restaurant is quick and steady on all three.

The three Core Web Vitals

MetricWhat it measuresGoodPoor
LCP: Largest Contentful PaintTime until the biggest image or text block in view is shown2.5 s or lessMore than 4 s
INP: Interaction to Next PaintDelay from a tap, click or key press until the screen updates (it looks at the slow interactions of the whole visit)200 ms or lessMore than 500 ms
CLS: Cumulative Layout ShiftHow much visible content jumps around without the user asking0.1 or lessMore than 0.25

Between good and poor is "needs improvement". INP replaced the older FID (First Input Delay) as a Core Web Vital in March 2024: FID only measured the wait before the first tap, while INP judges responsiveness across the whole visit. A page passes when the 75th percentile of real visits is good, which means at least 75% of visits must be good. So the slow phones in your audience count, not just your fast laptop.

Lab data and field data

Lab dataField data
Where it comes fromOne test run in a controlled setting (Lighthouse)Real visits by real Chrome users (the Chrome User Experience Report, CrUX)
Good forFinding and fixing problems while you buildKnowing what users really feel
INPNot measured; Lighthouse shows Total Blocking Time (TBT) as a hintMeasured

Lighthouse is built into Chrome DevTools (the Lighthouse panel). PageSpeed Insights (pagespeed.web.dev) shows both: field data from CrUX at the top when your site has enough visitors, and a Lighthouse lab report below. Run Lighthouse in an Incognito window with "Mobile" selected, because browser extensions and a fast desktop hide problems.

Fix 1: images, often the biggest win

html
<!-- The hero image is usually the LCP element: load it early -->
<img src="hero-800.webp"
     srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1600.webp 1600w"
     sizes="(max-width: 600px) 100vw, 800px"
     width="800" height="450"
     fetchpriority="high"
     alt="Fresh vegetables at a Pune market">

<!-- Images further down the page: let the browser wait -->
<img src="stall.webp" width="400" height="300" loading="lazy" alt="Fruit stall">
srcset and sizes pick the right file, width and height stop jumps, fetchpriority and loading set the order
  • Use modern formats: WebP works in every current browser, and AVIF is usually even smaller. A 2 MB phone photo can often become 100 KB or less.
  • Serve the size you show: a phone screen 400 pixels wide does not need a 4000-pixel photo. srcset lists the files and their widths, sizes says how wide the image will be shown, and the browser picks the best file.
  • Always write width and height (or set CSS aspect-ratio). The browser keeps the space ready, so text does not jump when the image arrives. This fixes most CLS problems.
  • loading="lazy" delays images far down the page until the user scrolls near them.
  • fetchpriority="high" tells the browser that the LCP image matters most.
Careful: Never put loading="lazy" on the hero image or anything visible at the top of the page. Lazy loading makes the browser wait before fetching it, so your LCP gets worse, not better.

Fix 2: less JavaScript, and short tasks

html
<!-- Blocks the page while it downloads and runs -->
<script src="app.js"></script>

<!-- Downloads in parallel, runs after the HTML is parsed, in order -->
<script src="app.js" defer></script>

<!-- Downloads in parallel, runs as soon as it is ready, in any order -->
<script src="analytics.js" async></script>
defer for your own scripts, async for independent ones like analytics. type="module" scripts are deferred by default.

Every kilobyte of JavaScript must be downloaded, parsed and run, and a budget phone runs it several times slower than a laptop. Remove libraries you do not need. Use code splitting: load a feature with a dynamic import() only when the user needs it, for example const { drawChart } = await import('./chart.js') inside a click handler. Vite and other bundlers create a separate file for each dynamic import.

javascript
// The main thread runs your JavaScript AND paints the screen.
// A task longer than 50 ms is a "long task": taps must wait, INP goes up.
function yieldToMain() {
  return new Promise((resolve) => setTimeout(resolve, 0));
}

async function processOrders(orders) {
  for (let i = 0; i < orders.length; i++) {
    processOne(orders[i]);
    if (i % 50 === 0) await yieldToMain(); // let the browser handle taps and paint
  }
}
Breaking a long task into pieces. Newer browsers also offer scheduler.yield() for this.
Tip: Show feedback first, do heavy work after. When a user taps "Add to cart", change the button at once, then do the slow work. The next paint happens quickly, so the tap feels instant and INP stays low.
  • Caching: send long Cache-Control headers for files with a hash in their name (app.3f9a1c.js), so repeat visits load them from the phone instead of the network.
  • CDN (Content Delivery Network): copies of your files on servers in many cities, so a user in Chennai downloads from a nearby server. Hosts like Netlify, Vercel and Cloudflare include one.
  • Fonts: add font-display: swap to @font-face so text shows at once in a fallback font, and load only the weights you use. System fonts cost nothing.
  • preconnect: <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> opens the connection to an important other server early.
  • Compression: text files should be sent compressed with gzip or Brotli. Most hosts do this for you.

Watch the lesson

Performance and Core Web Vitals · 1/8

Fast pages keep visitors

A slow page loses visitors, especially on budget phones and busy mobile networks.

Prefer a video by a teacher? Find free videos on “core web vitals LCP INP CLS explained lighthouse tutorial” on YouTube ↗

In real life: A grocery order page on a budget phone

A small grocery shop in Indore has an order page with a 3 MB banner photo and five chat and analytics scripts in the head. On a budget phone on 4G, the banner takes 7 seconds (poor LCP) and the "Add" buttons react slowly (poor INP). The developer converts the banner to a small WebP with fetchpriority="high", adds width and height to every product photo, lazy-loads the photos lower down, defers the scripts and removes two that nobody used. The page now passes Core Web Vitals and more people finish their orders.

Practice (0/3 done)

Task 1 · Code · HTML

This vegetable shop page is slow and jumpy. Fix it: (1) the hero image (id="hero") must NOT have loading="lazy", must have fetchpriority="high", and must have width and height attributes; (2) every image inside .gallery must have loading="lazy" plus width and height attributes; (3) the <script src="app.js"> must have the defer attribute.

Task 2 · Code · Javascript

Write rateVital(name, value) that rates one Core Web Vital. name is "LCP" (value in milliseconds), "INP" (milliseconds) or "CLS" (a score with no unit). Return "good", "needs improvement" or "poor" using these limits: LCP good up to 2500, poor above 4000; INP good up to 200, poor above 500; CLS good up to 0.1, poor above 0.25. A value exactly on a limit belongs to the better band: rateVital("LCP", 2500) is "good" and rateVital("INP", 500) is "needs improvement".

Task 3 · Practice

Measure a real site. Open a site you built (or one you use often) in a Chrome Incognito window, run Lighthouse in DevTools with "Mobile" selected, then test the same URL on PageSpeed Insights (pagespeed.web.dev). Write down the numbers and your top three fixes.

Quick quiz

Question 1

1.What is a good INP?

Question 2

2.Which metric replaced First Input Delay (FID) as a Core Web Vital in March 2024?

Question 3

3.Text jumps down when a photo loads above it. What fixes this best?

Question 4

4.Why should the hero image NOT use loading="lazy"?

Question 5

5.Lighthouse gives you lab data. What does field data add?

Read more (free)

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

← Previous: Debugging like a pro: DevTools and VS Code