Technical SEO
A Developer's Guide to Core Web Vitals
By Abid Ali · Published · Updated
What LCP, INP, and CLS measure, how to read them in the field and in Chrome, and the code-level fixes that move each one. No invented scores.

Core web vitals are three measurements Chrome collects from real visits. They describe loading, interaction, and visual stability. They are not a single grade, and a number from someone else's homepage is not your number. This guide is how to read the three metrics and what in the code usually moves them.
The current set:
- Largest contentful paint (LCP): when the largest visible element finishes painting. Often the hero image or the main heading.
- Interaction to next paint (INP): how long the page takes to paint after a tap or keypress, across the visit, not just the first click.
- Cumulative layout shift (CLS): how much visible content jumps around after it first appears.
Google's own thresholds change over time. Read the current documentation when you compare a report to a "good" line. Do not hard-code a passing score into a blog post and leave it there for years.
Measure before you edit
Two views, used for different jobs:
- Field. The Chrome User Experience Report, Search Console's Core Web Vitals report, or your analytics. This is the distribution of real phones and networks. A page can be fine on your laptop and poor for visitors.
- Lab. Chrome DevTools performance panel, or Lighthouse on your machine. One run. Use it to see which element was the LCP candidate and which node shifted. Do not treat one lab score as the field result.
In DevTools, record a load. The LCP marker names the element. If it is an image, that file is the first thing to fix. If it is a text block, the delay is usually a font or a script that blocked rendering, not the heading itself.
Largest contentful paint
Make the LCP element easy to fetch and hard to delay.
- Serve an image close to the size it is displayed at. A 2000px illustration in a 480px phone slot wastes the connection.
- Use a modern format (WebP or AVIF) and set width and height, or a reserved aspect ratio, so the browser can place it before the bytes arrive.
- Add
fetchpriority="high"only on that one image. Mark below-the-fold imagesloading="lazy"so they do not compete. - Do not hide the hero behind client-only rendering. If the LCP image is inserted in
useEffect, it cannot paint until JavaScript runs. - Keep
metadataand fonts from blocking the response. A huge render-blocking script above the image delays LCP even when the image file is small.
A responsive picture element does this split cleanly: a smaller file under a media query for the phone, a larger one for the desktop hero, the phone file lazy if a different image is the desktop LCP. The point is one priority image, not every image urgent.
OG Scope's server-rendered page is the same principle in another shape. The form is in the first HTML. There is no wait for a client bundle before the main content exists. Client JavaScript can enhance it later.
Interaction to next paint
INP suffers when a tap has to wait on a long task. Typical causes:
- Too much JavaScript on the interaction path. A client-rendered page parses a large bundle before a menu can open.
- A click handler that does heavy work synchronously: filtering a large list, parsing a big JSON blob, writing layout in a loop.
- Third-party scripts on the main thread.
Move work that does not need the user's gesture off the click. Keep the client boundary small so the browser is not still compiling the article's code when someone opens the menu. If a list is large, filter it in a way that does not block the paint, and show the result on the next frame.
INP is not "time to first byte" and it is not the old First Input Delay metric. A page can load quickly and still feel late on the second tap.
Cumulative layout shift
CLS is a jump. The usual sources:
- Images, iframes, and ads without a reserved size. The text below them moves when the media arrives.
- A font that swaps and changes line breaks.
- Content inserted above what the user is looking at (a banner, a cookie bar that pushes the page).
- A desktop hero and a mobile hero both in the document without reserved space, so one pops in.
Set width and height on images. Give embeds a ratio. Put late banners in space you already reserved, or overlay them without pushing content. A shift the user caused, such as opening an accordion, does not count the way an unexpected jump does. The report is about stability they did not ask for.
A developer's order of work
- Read field data for the URL you can actually change.
- In the lab, name the LCP element and any shifting node.
- Fix that element: size, priority, reserved space, or a smaller client bundle.
- Ship, then look at field data again after it has had time to collect. A single Lighthouse run the same afternoon is not the new field score.
Core web vitals are a feedback loop on largest contentful paint, interaction to next paint, and cumulative layout shift. The fixes are specific files and attributes. A score without the element that produced it is not an action.
Questions
- Which number should I trust, the lab test or the field data?
- Field data is what real visitors experienced. A lab run is one machine, one network, one moment. Use the lab to debug. Use field data (CrUX or your own analytics) to decide if the page is actually slow.
- Do Core Web Vitals require a specific framework?
- No. Largest contentful paint, interaction to next paint, and cumulative layout shift are browser metrics. Next.js, plain HTML, and other stacks all produce them.
- Can I copy a score from someone else's audit?
- No. A score belongs to a URL, a device, and a time. Publish a number only after you measure that URL. This guide describes what to change, not a result for any site.