The Lighthouse score on a dental client's homepage in late April: 94 mobile, 98 desktop. Green across the board. The actual field data, pulled from the Chrome User Experience Report a week later: 22% of real users on that page were getting an INP score in the red. The lab said the site was fast. Real visitors said it wasn't. Both were telling the truth — they were just measuring different things.

This is the gap that matters now. Lighthouse is a synthetic test on a simulated mid-tier phone. Google's actual ranking signal comes from CrUX field data, aggregated from real Chrome users. If those two diverge, the field data wins.

What changed

The headline: Interaction to Next Paint, INP, has fully replaced First Input Delay as a Core Web Vital. FID was a simpler metric that measured only the delay before the browser started processing the first interaction. INP measures the full round trip — from input to visible response — across all interactions on the page, taking the worst-case (or near-worst-case) experience as the score.

The "good" threshold for INP is 200 milliseconds at the 75th percentile of real users. "Needs improvement" is 200 to 500. Anything over 500 is poor. A page can fail INP if even 26% of users have slow interactions, which is a much lower bar than FID used to set.

LCP thresholds didn't change officially, but the bar's been quietly raised in practice. The 75th percentile threshold is still 2.5 seconds for "good." What's changed is enforcement — sites that are right on the line are getting tagged "needs improvement" more often because the mobile network mix in CrUX has shifted to a slower realistic baseline.

CLS is unchanged at 0.1.

Stop trusting Lighthouse alone

The biggest mistake we see is treating PageSpeed Insights' Lighthouse number as the metric. The number in PageSpeed Insights' "Diagnose performance issues" section, labeled with the small "field data" tag — that's the one. If you're not in CrUX (you need a minimum amount of real Chrome traffic), you have no field data, which is its own problem: Google has no ranking signal to apply, but you also have no way to know what real users are experiencing.

For sites without enough CrUX data, we install a small piece of real user monitoring — the web-vitals JavaScript library piping into a Cloudflare Worker that aggregates per-URL daily, no third-party RUM tool needed, no PII collected. Costs roughly nothing to run and tells you what your actual visitors see.

The fixes that paid off

Three changes have made the biggest difference across the sites we've worked on this quarter.

Defer third-party scripts you didn't realize you had. The biggest INP killer we keep finding is a long-running task triggered by a marketing tag fired on first interaction. Hotjar, Meta Pixel's advanced matching, and a couple of CRM scripts in particular. We now load all non-essential third-party scripts with type="module" and load them after the page is interactive using a small loader that waits for requestIdleCallback. For the dental client, this alone dropped 75th-percentile INP from 380ms to 165ms.

Image lazy loading, done correctly. Native loading="lazy" on every image below the fold. fetchpriority="high" on the LCP image, which is typically the hero. decoding="async" everywhere. Width and height attributes on every image so the layout doesn't shift when they load. Most sites we audit do one or two of these. Doing all four moves LCP and CLS at the same time.

Break up long JavaScript tasks. INP is sensitive to single tasks that block the main thread for more than 50 milliseconds. We've found two common culprits: client-side analytics queueing logic that processes a batch on every click, and React/Vue re-renders triggered by a state change that fans out across the page. The fix is usually to yield to the browser during long tasks using scheduler.yield() where it's available, or setTimeout(fn, 0) as a fallback. On a Next.js client project we cut INP by 60% by adding a single await scheduler.yield() inside a search-filter handler.

What stopped working

Preloading everything. We used to preload fonts, hero images, and the next likely page. Done aggressively, this just shifts contention earlier in the page load and can hurt LCP. We now preload only the LCP image and, on auth-required apps, the user-data endpoint. Everything else is fetched at its natural time.

Aggressive code splitting. Splitting a small site into twenty chunks introduces request waterfalls that hurt INP more than the parsing cost they're meant to save. For most small-business sites we now ship one or two JavaScript bundles and stop there.

Service workers as a performance fix. They can help repeat visits but they can also tank first-visit metrics and add caching bugs that are hard to debug at 11pm on a Sunday. We only ship a service worker now when the client actually needs offline-capable behavior, not as a generic performance trick.

What we'd fix first on a site that's red

In order of bang for the buck:

  1. Pull the field data, not the lab data. If you don't have CrUX, install web-vitals and wait two weeks.
  2. Identify the LCP element. PageSpeed Insights will tell you. Make sure it has fetchpriority="high", has explicit dimensions, isn't lazy-loaded by mistake, and isn't blocked behind a script or a font swap.
  3. Open the Performance tab in Chrome DevTools while clicking around the page. Look for tasks longer than 50ms. The flame graph almost always points to a third-party script or a heavy framework re-render.
  4. Defer non-essential third-party JavaScript.
  5. Re-test after two weeks of real traffic. INP especially needs time to stabilize in CrUX.

The pattern we keep seeing is that the lab tools and the field data tell different stories, and Google ranks on the field. A site that scores 95 in Lighthouse but ships a Hotjar bundle that runs a 600ms task on every click is failing Core Web Vitals on real users' phones, and Google will treat it accordingly. The fix is almost never something exotic. It's usually a script someone added in 2022 and forgot.