Decompose the metric, not the checklist
A 420 ms INP is three timings, and only one of them is worth your effort.
Do not optimize what a lab tool flags. Optimize what your field data proves is failing at the 75th percentile, and here is exactly how much each fix is worth. INP is input delay plus processing plus presentation. Attribute the overshoot to one subpart, then pull one lever.
Core Web Vitals optimization 2026, diagnosed from field data
Do not optimize what a lab flags. Read your field data at p75, attribute the failure to one subsystem, pull the one fix, then measure the millisecond delta and the revenue it moves.
Why start from field data instead of a 20-item checklist?#
Search this topic and every top result hands you the same list. What that list never asks is whether your page needs any of it. You apply generic fixes and hope one lands. In practice, that is expensive guessing.
This guide inverts the genre, turning Core Web Vitals optimization 2026 into a diagnostic loop rather than a prescription. You read the field, attribute the failure to one subsystem, then pull the one fix, measure the delta, and model the revenue. Branching on data means you never spend effort a metric did not ask for.
Read the loop as an order, not a menu. It starts from the field, never the lab, and forces attribution before action. The fix that survives is always the one your numbers demand.
Prescription-first vs diagnosis-first: why the generic list wastes your time#
A checklist is prescription-first. It lists fixes with no link to your data, so you cannot tell which item is your bottleneck. Most items will not apply to your page at all.
Diagnosis-first flips the order. It asks which metric fails in the field, then which subsystem owns that metric. A slow first byte is a server problem, so backend query bottlenecks that inflate server response time matter more than any image trick. Attribute first and you stop pulling levers that were never loose.
The 75th percentile is the only number that decides#
Core Web Vitals pass or fail at the 75th percentile. Seventy-five percent of real visits must clear the good line. An average hides your failing quartile, and a single lab run hides it completely. p75 is the grade that ships.
Chrome exposes this in the Chrome User Experience Report, so read your metric there before you act. The CrUX documentation explains how the field dataset is collected from real Chrome users. Being real-user data, it is the verdict that counts.
The routing table: which fix does your failing metric actually need?#
Attribution needs a reference. This is where Core Web Vitals optimization 2026 turns into a routing engine competitors skip. Read down to your failing subpart, then across to the one fix, the typical millisecond delta, and the effort it takes.
| Failing metric and subpart | Most likely cause | The one fix | Typical ms delta | Effort |
|---|---|---|---|---|
| LCP: TTFB | Most likely causeSlow server or origin round-trip | The one fixCache or render at the edge | Typical ms delta400 to 1200 ms | EffortMedium |
| LCP: load delay | Most likely causeLCP image discovered late | The one fixPreload with fetchpriority=high | Typical ms delta200 to 600 ms | EffortLow |
| LCP: load duration | Most likely causeOversized or legacy-format image | The one fixAVIF or WebP at the rendered size | Typical ms delta300 to 900 ms | EffortLow |
| LCP: render delay | Most likely causeRender-blocking CSS or JS | The one fixInline critical CSS, defer the rest | Typical ms delta150 to 500 ms | EffortMedium |
| INP: processing | Most likely causeOne long main-thread task | The one fixYield after ~50 ms, break the task up | Typical ms delta80 to 300 ms | EffortMedium |
| CLS: shift source | Most likely causeUnsized media or a late banner | The one fixReserve space with aspect-ratio | Typical ms deltascore to ~0 | EffortLow |
Read the effort column as a tie-breaker. Two low-effort levers clear most LCP problems, so you rarely start with the medium ones. Exhaust the cheap moves, then re-measure the field.
INP: the core web vitals optimization 2026 centrepiece#
INP replaced FID as the responsiveness metric in March 2024. Most guides still explain it as a single number, which misses the mechanics entirely. It is the least explained Core Web Vital, and the one most driven by third-party scripts and hydration cost.
The web.dev launch note marks the change from FID to INP. Measuring the full interaction rather than just the first input makes INP harder to fake and harder to fix. This section owns the mechanics.
Input delay + processing time + presentation delay: 420ms, broken apart#
INP is three timings, not one. Input delay runs while the thread is busy with other work. Processing time runs while your handler executes. Presentation delay runs while the next frame renders. Together they make the number.
Consider a real 420 ms INP, the one in the hero above. It splits into 120 ms of input delay, 250 ms of processing, and 50 ms of presentation. The processing chunk is the largest and the most reducible, which makes it the subpart to attack. Presentation delay, by contrast, is a near-fixed frame cost. The web.dev INP guide defines all three phases in full.
The INP Task Lab: insert yield points and watch INP fall#
A decomposition is easier to feel than to read. Fire a synthetic interaction, then drag the slider to insert scheduler.yield() breakpoints. As breakpoints rise, the long task splits into shorter chunks. The measured INP falls from about 420 ms toward about 180 ms, live, split across the three subparts.
The 340 ms handler, split into 1 chunk
Each yield ends a task, so the browser can accept the next interaction and paint between chunks. Shorter chunks mean a shorter wait and a shorter path to the next frame.
Good line: 200 ms. Poor above 500 ms.
| Yields | Input delay | Processing | INP total |
|---|---|---|---|
| 0 | 120 ms | 250 ms | 420 ms |
| 1 | 80 ms | 145 ms | 275 ms |
| 2 | 67 ms | 110 ms | 227 ms |
| 3 | 60 ms | 93 ms | 203 ms |
| 4 | 56 ms | 82 ms | 188 ms |
| 5 | 53 ms | 75 ms | 178 ms |
| 6 | 51 ms | 70 ms | 171 ms |
| 7 | 50 ms | 66 ms | 166 ms |
| 8 | 49 ms | 63 ms | 162 ms |
0 yield points: the 340 ms handler runs as 1 chunk, longest about 340 ms. Estimated INP 420 ms, made of input delay 120 ms, processing 250 ms, presentation 50 ms. Verdict Needs improvement.
An illustrative planning model, not a benchmark. It assumes each yield point splits the handler into one more chunk, that shorter tasks cut both the input delay and the processing before paint, and that presentation delay is a fixed frame cost. Real numbers move with your device, your other scripts, and your hydration cost. Show 420 ms here, then attribute your own slow interaction with the Long Animation Frames API before you ship a fix.
Notice which subparts move. Yielding shortens the longest task, so input delay and processing fall together while presentation delay holds near its floor. The lever is scheduling, not a faster function.
Mapping each INP component to its code fix#
Each phase maps to a distinct change. Here is the causal chain in code. Switch tabs to compare the monolithic long task, the yielded version, and the deferred version. Each panel is the exact pattern that moves one subpart.
// One click handler doing ~340 ms of synchronous work on the main thread.
button.addEventListener('click', () => {
const parsed = parsePayload(raw); // ~90 ms
const items = buildResults(parsed); // ~180 ms
renderList(items); // ~70 ms of DOM writes
});
// The browser cannot paint or accept a new interaction until this
// returns. So INP = input delay + this whole 340 ms + presentation.
// Under load the measured INP lands near 420 ms, past the 200 ms line. // Insert scheduler.yield() breakpoints, so each task stays short.
button.addEventListener('click', async () => {
const parsed = parsePayload(raw);
await scheduler.yield(); // hand the thread back, then resume
const items = buildResults(parsed);
await scheduler.yield(); // browser can paint + take input here
renderList(items);
});
// Each chunk is short, so the interaction starts sooner (input delay
// falls) and paints sooner (processing falls). INP drops toward ~180 ms. // Not every handler needs to run before the next frame. Defer the work
// that does not affect what the user sees on this interaction.
button.addEventListener('click', () => {
applyVisualState(); // the ~30 ms the user actually waits on
// Non-critical: analytics, prefetch, logging. Push it off the
// interaction path so it never counts toward INP.
requestIdleCallback(() => {
sendAnalytics();
prefetchNextView();
});
}); Read the tabs as a progression. Chunk the task first, and the interaction starts and paints sooner. Defer whatever the user does not wait on next. Analytics and prefetch never affect the visible result, so moving them off the interaction path drops them out of INP entirely.
LCP: what actually moves it in 2026#
LCP is the load metric. It marks when the largest element paints. Generic advice stops at "optimize images" and moves on. This section is concrete about what moves LCP now.
Find the LCP element, then make it arrive first#
Attribution comes before action here too. Identify the LCP element from field data, since it is often not the element you assume, then trace which subpart holds it back. A large share of the budget is usually spent before the browser paints anything at all, on the first byte. To see where that byte comes from, read the request lifecycle behind every page load. For the rendering side of that first byte, edge rendering that cuts Time to First Byte on Cloudflare attacks the largest LCP subpart directly.
fetchpriority, preload, and modern image delivery#
Two low-effort moves clear most LCP problems. Preload the LCP image and mark it high priority, so the browser starts it early. Then serve a modern format at the rendered size, so you download only the bytes you paint.
<!-- Preload the LCP image and mark it high priority, so the browser
starts it in the first flight, not after CSS and layout settle.
This attacks resource load delay, the discovery gap. -->
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high" />
<!-- Serve a modern format at the rendered size, so load duration is
bytes you actually paint, not a full-resolution PNG. -->
<img
src="/hero.avif"
srcset="/hero-800.avif 800w, /hero-1200.avif 1200w"
sizes="(max-width: 800px) 100vw, 1200px"
alt="Product hero"
width="1200"
height="630"
fetchpriority="high"
/> Discovered and prioritised early, the image shrinks its resource load delay, and the AVIF at the rendered size cuts load duration. The web.dev fetch priority guide covers the priority-hints API, and the optimize LCP guide covers the full subpart split.
CLS: eliminating layout shift at the source#
CLS is the stability metric. It scores how much visible content jumps while the page settles. A shift only counts when visible content moves, so the fix is almost always to reserve space up front.
Reserved space, font-display, and late-injected content#
Read the pattern behind all three. In every case, the shift happens because a box arrives without a reservation. Reserving the box removes the cause, not just the symptom.
The critical rendering path, and exactly what blocks it#
LCP render delay and much of load delay trace to one thing: the critical rendering path. It runs from the first byte through parse, style, layout, and paint. Some resources block that path, and the first pixels wait on them.
Notice the two red nodes. Render-blocking CSS and synchronous JS both sit between parse and paint, which pushes out every load-side metric. Deferred scripts run afterward and never block the first frame.
Render-blocking CSS and JS, and how to unblock each#
Each blocking node has a matching move. Inline the critical CSS and load the rest asynchronously, so the render tree does not wait on a full stylesheet. Add defer or async to scripts, so parsing is not paused mid-document. Code-split the bundle, so a route ships only the JavaScript it needs. Each move clears one blocking node, and the first paint arrives sooner. For the perceived-speed side of this, progressive web app patterns that improve perceived performance cover instant navigation and offline shells.
Why Lighthouse says 98 while your users are failing#
A green Lighthouse score is not a passing grade. A lab tool runs one synthetic load, so it misses the range of real devices and networks. A lab pass can sit right next to a field failure. Step through why.
Lighthouse runs one throttled load
The lab test uses a single synthetic device and network profile. It is reproducible, but it is one sample, not your audience.
It has no real user to interact
A lab tool cannot click, type, or scroll like a person. Therefore it cannot measure INP honestly, because INP needs a real interaction.
CrUX reads 75 percent of real visits
The field dataset aggregates real Chrome users at p75. So it captures the slow devices and busy main threads the lab never saw.
The verdict is the field, every time
When lab and field disagree, trust the field. A Lighthouse 98 with a failing CrUX INP means real users are waiting, so the field is where you act.
Read the INP step as the key case. A lab has no real input, so it cannot grade responsiveness. Field data is the only honest source for INP.
Measuring for real: CrUX for the verdict, RUM for attribution#
Two field sources do two jobs. CrUX and PageSpeed Insights give the p75 pass or fail verdict. Your own RUM gives the attribution, since it can tag the exact element or script. Read the verdict in CrUX, then attribute the cause in RUM.
For INP attribution, the Long Animation Frames API names the script that held the main thread. For CLS, the Layout Instability API logs each shift source. These tie a number to a line of code, turning a failing metric into a specific fix. The Core Web Vitals overview explains the lab and field split in full.
What a Core Web Vitals win is actually worth#
Speed is not vanity. Rakuten 24 ran a controlled test to prove it, which makes the payoff measurable rather than a slogan. The chart shows the business deltas from that test as scannable magnitudes.
Show data table
| Item | Change against the unoptimized variant |
|---|---|
| Revenue / visitor | 53.37% |
| Conversion rate | 33.13% |
| AOV | 15.2% |
| Time spent | 9.99% |
| Exit rate cut | 35.12% |
On one landing page split 50/50 for a month, the Core-Web-Vitals-optimized variant moved revenue per visitor by 53.37 percent and conversion by 33.13 percent, with no visual or functional change beyond the performance work. These are the published web.dev Rakuten 24 figures, not a benchmark of your page.
Read the numbers as leverage, not a promise. The only change was the performance work, so the deltas are attributable to it. Core Web Vitals optimization 2026 is a conversion lever, not just a lab score.
The Rakuten 24 controlled test, in real numbers#
The setup was clean. Rakuten 24 split one high-traffic landing page 50/50 for a month. Version A was optimized for Core Web Vitals. Version B was the original, with no visual or functional difference beyond the performance work.
The field result was concrete. Version A finished loading 0.4 seconds earlier on the mobile test and showed no significant layout shift. That single change crossed the line, and the business outcome followed. Correlating field vitals against revenue separately, a good LCP bucket was associated with up to a 61.13 percent higher conversion rate versus the site average. The web.dev Rakuten 24 case study documents the full method and figures.
Model your own page: the budget + revenue calculator#
A published case study is not your page, so enter your own numbers instead. Set your traffic, your average order value, and your current LCP and INP subpart timings. Read which subparts overshoot budget, and the revenue a crossing to good would model on your inputs.
If this page crossed to good, on your own numbers
- Modeled conversion uplift
- 8.7%
- Extra revenue / month
- $20,045
- Extra revenue / year
- $240,538
Current modeled revenue is about $230,400 a month. The uplift scales with how far LCP and INP sit over budget, capped at 30.0%. For context, Rakuten 24's controlled test moved revenue per visitor by 53.37% on a single optimized page, so this cap stays deliberately conservative.
| Subpart | Current | Budget | Over budget? |
|---|---|---|---|
| TTFB | 1100 ms | 800 ms | yes, by 300 ms |
| Load delay | 300 ms | 250 ms | yes, by 50 ms |
| Load duration | 1400 ms | 1000 ms | yes, by 400 ms |
| Render delay | 500 ms | 450 ms | yes, by 50 ms |
| Input delay | 120 ms | 50 ms | yes, by 70 ms |
| Processing | 250 ms | 100 ms | yes, by 150 ms |
| Presentation | 50 ms | 50 ms | no |
LCP total 3300 ms (Needs improvement), INP total 420 ms (Needs improvement). 6 of 7 subparts are over budget. Modeled conversion uplift if you cross to good: 8.7%, worth about $20,045 a month and $240,538 a year on 120,000 monthly visits at $80 order value.
An illustrative planning model, not a benchmark. Budgets are the published good ceilings (LCP 2.5 s, INP 200 ms) split across subparts; the revenue model applies a conservative, capped conversion uplift to your own traffic and order value. Real outcomes depend on your audience, funnel, and pricing, so read your own p75 in CrUX and run a controlled test before you bank any figure here.
Notice how the money tracks the overshoot. The uplift scales with how far LCP and INP sit over budget, so the worst subpart is worth the most to fix. Tuning a subpart already inside budget models nothing. Read your own p75 in CrUX before you fund any lever.
When NOT to reach for these fixes#
There is one more limit to name. Sometimes the real problem is not a subpart at all. A page that ships a megabyte of unused third-party script cannot be rescued by any single lever. What it needs is a script budget and a hard look at what each tag earns. Core Web Vitals optimization 2026 tunes the critical rendering path; it does not excuse a bloated page. Set a budget first, then run the loop.