Core Web Vitals in 2026: How to Measure LCP, INP and CLS With Real Data (Not Lighthouse Alone)
Core Web Vitals thresholds didn't change in 2026 (LCP 2.5s, INP 200ms, CLS 0.1), but how they are measured and debugged did. This guide is for pulling your real numbers out of CrUX and Search Console, finding the failing interaction with LoAF and fixing only what moves the needle.
And there is one concrete change this month: single-page applications are no longer a blind spot.
What Core Web Vitals Are (and What They Are Not)
Core Web Vitals are the subset of web performance metrics Google considers applicable to every page and measures with real user data. There are three, and each covers a different dimension.
LCP: How Long Until the Important Thing Shows Up
Largest Contentful Paint measures the time until the largest content element inside the viewport is painted: usually the hero image, a big headline or a block of text. It is the loading metric.
Read also
INP: How Long Until the Interface Answers You
Interaction to Next Paint measures the time from a user interaction (click, tap or keypress) until the interface paints the next frame with the response. It replaced First Input Delay on March 12, 2024, and FID was deprecated. It is the interactivity metric and, today, the one most sites fail.
CLS: How Much What You Were Reading Moves
Cumulative Layout Shift measures visual instability: how much content moves without the user causing it. A banner that appears on top of the paragraph you were reading is pure CLS.
Thresholds: Same Numbers, Measured at the 75th Percentile
The thresholds did not change in 2026:
- LCP: 2.5 seconds or less.
- INP: 200 milliseconds or less.
- CLS: 0.1 or less.
The part almost nobody explains is how a site is graded. Your laptop and your connection are not used: the number comes from field data and is evaluated at the 75th percentile. That means 75% of visits did better than that value; the remaining 25% can be far worse and it does not count. That is why a pretty average can hide a mobile disaster.
The Data Google Looks at Is Not Your Laptop's
Field Data vs Lab Data
Field data comes from real users, on their own devices and networks. Lab data is generated in a controlled environment. Google's verdict on your site is based on field data.
What CrUX Is and Why Your Site May Have No Data Yet
CrUX (Chrome User Experience Report) is the aggregated dataset Google uses to measure Core Web Vitals. It updates monthly, and an origin only shows up if it has enough traffic; if your site is new or small, it simply is not there, and that does not mean it is slow: it means there is no measurement.
Lighthouse Is for Diagnosing, Not for Passing
Lighthouse measures lab data. As a performance score it is excellent for reproducing problems and checking that a fix broke nothing, but the number Google evaluates does not come from there. Confusing the two is the most common mistake and the one that wastes time chasing a perfect 100.
How to Measure Your Site With Real Data, Step by Step
Step 1: Read the Core Web Vitals Report in Search Console
It groups URLs by status and page type, so it is useful for seeing patterns per template, not for a single URL. Start with the group that has the most affected URLs: that is where the biggest return is.
Step 2: Confirm With PageSpeed Insights and the CrUX API
PageSpeed Insights blends CrUX field data with a lab audit. If your site has little traffic, it may show no field data at all and you will only see lab results: keep that in mind before drawing conclusions. The CrUX API, and the BigQuery dataset, let you query and chart your own history.
Step 3: Measure With the web-vitals Library on Your Own Site
The direct route is measuring yourself. The official web-vitals library (version 6) exposes one function per metric and an attribution build that tells you, beyond the value, which element or interaction misbehaved. The example below is the minimal attribution pattern: every metric is sent to your own endpoint.
import {onCLS, onINP, onLCP} from 'web-vitals/attribution';
function send({name, value, delta, id, attribution}) {
let target = attribution.target;
if (name === 'INP') target = attribution.interactionTarget;
if (name === 'CLS') target = attribution.largestShiftTarget;
navigator.sendBeacon('/rum', JSON.stringify({name, value, delta, id, target}));
}
onCLS(send);
onINP(send);
onLCP(send);Two practical details: do not call these functions repeatedly per page load without a reason (each one creates its own observer) and remember INP is never reported if the user does not interact with the page.
Step 4: Read the Attribution (Element, Event and Handler)
A value with no context says very little. With the attribution build you get the offending element's selector for LCP and CLS, and for INP the target element, the event type and the time split by phase. With that, the conversation stops being "the site is slow" and becomes "this button, on this kind of event".
How to Debug What Fails
INP: Input Delay, Processing and Presentation
INP is made of three parts: input delay (how long the interaction waits before it starts being processed), processing (your code) and presentation (painting the result). If input delay is the big slice, the problem is almost never in your event handler, but in the script that was already running when the user clicked.
The LoAF API: Who Blocked the Thread and Where the Time Went
Long Animation Frames is the evolution of the Long Tasks API and the reference tool for debugging INP: it attributes blocking work to specific scripts and phases, and separates time spent in script execution from time spent in style and layout. That turns suspicion into a list of culprits.
Long Tasks and scheduler.yield(): the Await Detail
To split work you use scheduler.yield(). The warning most people skip: that function only schedules the task, the await is what actually pauses execution. And watch out for iterative methods: forEach does not wait for each callback to finish, so a forEach with an await inside does not do what it looks like.
async function runJobs(jobs) {
for (const job of jobs) {
job();
await scheduler.yield();
}
}Yielding at every step also has a cost. The sensible approach is batching work and yielding only when the task has been running for a while, with a common 50-millisecond deadline to keep tasks from becoming long tasks. And since not every browser supports it yet, a fallback with setTimeout wrapped in a promise is worth having.
LCP: Separating TTFB, Resource Load and Rendering
For LCP, first identify the element. Then look at which stage eats the time: if TTFB dominates, the problem is the server; if resource loading dominates, it is the image or its weight; if rendering dominates, it is usually CSS or JavaScript blocking ahead of the main content.
CLS: Images Without Dimensions, Banners and Fonts
The usual causes: images without declared width and height, blocks injected (alerts, banners, cookie notices) on top of already painted content, and web fonts that change text size as they load.
How to Fix Each Metric
LCP: Server, Images and Critical CSS
Order of attack: first TTFB, with page caching and a decent server response; then images, with correct dimensions, a modern format, the right size per screen and high priority only for the main image; finally critical CSS, and keeping the main content from waiting on fonts or scripts.
If your site runs Laravel, the server stage is already covered in the post about page caching and TTFB in Laravel 13, so we won't repeat it here.
INP: Less JavaScript, Less Work Inside the Handler
What moves the needle most: trimming third-party scripts, deferring and splitting what is not critical, avoiding heavy work inside the event handler and not repainting more than needed. If you use Livewire or Alpine, update small blocks instead of entire frames. The lazy loading and code splitting techniques are the natural next step.
CLS: Reserve the Space Before Painting
Declare dimensions or aspect ratio, reserve fixed space for banners and notices, pick the right font loading behaviour and never insert content above what the user is already reading.
What's New in 2026: Measuring Soft Navigations Too
Why Single-Page Apps Were Blind
Until now, when you navigated inside a single-page application, measurement only recorded the initial load: later view changes were not treated as navigations, and every interaction in the session was attributed to that first page.
What Changes With Soft Navigations (and What Doesn't)
Chrome 151 shipped the Soft Navigations API and the web-vitals library added support in version 6, through the reportSoftNavs option. Starting this month, CrUX publishes experimental variants of all three metrics that include soft navigations.
import {onCLS, onINP, onLCP} from 'web-vitals';
onCLS(console.log, {reportSoftNavs: true});
onINP(console.log, {reportSoftNavs: true});
onLCP(console.log, {reportSoftNavs: true});The numbers from the September 2026 cut, published on October 13, show the size of the shift: across 18.37 million origins, 68.3% had good LCP, 81.7% good CLS, 85.4% good INP and only 55.9% passed all three. Including soft navigations, those figures rise to 72.1%, 85.1%, 87.4% and 59.5% respectively. Read it carefully: sites did not get faster, a different population is being measured.
What does not change: thresholds stay the same and Google's stable numbers did not move while these variants remain experimental. The Chrome team aims to graduate them in May 2027, and only then will Search Console switch to the new data. What to do now: update the library, label your internal routes and start comparing your own before and after.
The Laravel 13 / Blade / Tailwind Case
If the site is Laravel with Blade, Tailwind and Vite, this is the order that works. TTFB first: page or response caching, cached queries and no heavy work at boot. Then properly served responsive images and @vite without blocking rendering. Then deferred JavaScript and small components. And finally reserved slots in Blade for anything that appears later (notices, banners, JavaScript-loaded content), which is where CLS problems are usually born in this stack.
Checklist: What to Do on Your Site This Week
- Look at Search Console grouped by page and pick the worst template, not the worst URL.
- Confirm with PageSpeed Insights whether field data exists; if it does not, there is no verdict to give.
- Install the web-vitals library with attribution and collect at least a week of your own data.
- Reproduce the case with DevTools and the LoAF API: find the script and the phase, not the feeling.
- Fix one thing, measure the same page type and compare the 75th percentile before and after.
- Keep Lighthouse CI in the pipeline as a safety net only, never as the goal.
And how not to fool yourself: change one variable at a time, measure the same page type and never trust an improvement that only shows up on your laptop. Changing the metric is easy; changing the distribution of users who live with it is what adds up.
Conclusion
Measure with real data before touching code, fix the right stage and measure again. The 2026 thresholds are the same as always; what is new is that internal navigations in single-page applications can now be measured too.
If the technical side of SEO interests you, the blog has a guide to Laravel 13 technical SEO, and if your bottleneck is the server, start with the post on page caching and TTFB. Keep reading Blender Deluxe for the next step: measure, fix and verify.


