You've seen Core Web Vitals (especially LCP) turn green in Lighthouse, but Search Console's real-user data still says red? You're not alone. Lighthouse's default mode doesn't actually slow the page down — it simulates CPU and network constraints, which is why your lab score and your field (CrUX) score don't match. This post separates three different "truths" (lab, field, real trace), shows where Lighthouse's simulation engine can be wrong, and walks through capturing a real trace in DevTools Performance with 4x CPU + Slow 4G; toward the end you'll also see how to fold Next.js 16.3's "Instant Insights" into your measurement workflow.
💡 Pro Tip: The trace you see when you click "View Original Trace" in the Lighthouse panel is the raw recording before simulation — so the time values you see there don't line up one-to-one with the metric results Lighthouse reports.
Table of Contents
- Three separate "truths": lab, field, and real trace
- Quick comparison of the data layers
- Where simulated throttling gets it wrong: three concrete limits
- Capturing a real trace with 4x CPU + Slow 4G emulation
- Verifying the LCP element with visual proof
- p75 vs average: which number you should trust
- Using Next.js 16.3's Instant Insights as a measurement tool
- A regression gate in CI: testing navigation with instant()
- What to keep server-side LHCI/PSI for, and what not to
- Which tool answers which question
- FAQ
- Why does my Lighthouse score differ from real user data?
- How do I measure LCP with a real Chrome trace?
- What is simulated throttling, and why can it deviate from real measurement?
- How do I catch CWV regressions in CI?
- Is "Slow 4G" in Lighthouse a real 4G connection?
- Why does the difference between p75 and the average matter?
- Conclusion
- Sources
Three separate "truths": lab, field, and real trace
When people talk about Core Web Vitals, they're actually talking about three different data layers, and they are not interchangeable:
- Lab data: a one-off measurement DevTools or Lighthouse produces in a controlled environment. It's deterministic, CI-friendly, but represents a single device/network scenario.
- Field data: the anonymous measurement Chrome UX Report (CrUX) collects from real users. Google's official definition is: thresholds are evaluated "at the 75th percentile of page loads, split across mobile and desktop devices." Search Console, PageSpeed Insights, and the "Field data" section of the DevTools Performance panel show this data.
- Real trace: a live recording taken from a session where CPU and network throttling are actually applied, in the DevTools Performance panel. Not a simulation — it's literally what happened in the browser at that moment.
The official thresholds are the same across all three: 2.5 seconds for LCP, 200 milliseconds for INP, 0.1 for CLS — defined as the "good" user experience boundary in web.dev's Core Web Vitals documentation. Which layer you measure in doesn't change, but _which number is correct_ changes depending on the measurement method — and that's exactly this post's thesis.
Quick comparison of the data layers
Layer | Source | Determinism | Best use |
|---|---|---|---|
Lab | DevTools / Lighthouse (Simulated) | High (repeatable) | CI, daily trend, fast feedback |
Field (CrUX) | Real users, p75 | Variable, updates slowly | Official CWV assessment, search ranking signal |
Real trace | DevTools Performance (throttled) | Medium (depends on your conditions) | Root cause analysis, proving the LCP element |
Where simulated throttling gets it wrong: three concrete limits
Lighthouse's official throttling documentation is blunt about it: "Lighthouse applies network throttling to emulate approximately the 85th percentile of mobile connection speeds, even when run on much faster fiber connections." This constraint isn't a real network test; "Simulated throttling is the method Lighthouse uses by default, and it uses a page load simulation based on data observed from an initial unthrottled load."
In practice this can mislead you in three places:
- The naming is confusing. The preset called "Slow 4G" in Lighthouse actually represents "the slowest 25% of 4G connections combined with the fastest 25% of 3G connections" — and per the official docs, it used to be labeled "Fast 3G." The name changed while the numbers stayed the same; don't assume "Slow 4G" means a real 4G connection.
- The comparison point is flawed too. DevTools (request-level) throttling, the alternative to simulation, applies delay at the request level rather than the packet level as on real mobile connections; of it the official docs say, "As a result, this throttling isn't highly accurate." So don't assume you're measuring the simulation against a "real" reference.
- It drifts in edge cases. The same doc notes simulated results are "equal or better accuracy" than DevTools throttling on most sites, but "suffers on edge cases," and a deep performance investigation should use packet-level throttling tools.
In short: simulated throttling is a fast, deterministic estimate, not a real measurement device. Great for fast CI feedback, but it can fall short as a definitive answer to "why is this page slow?"
Think of it as an analogy: simulated throttling is a prediction model that watches the initial unthrottled load and says "this page will probably behave like this on 4G." Same input, same output every time — valuable for avoiding flaky CI results. But the prediction can't capture real-world variability (Wi-Fi to cellular switches, a background tab, the device's thermal state) — the "suffers on edge cases" phrasing already admits this.
Capturing a real trace with 4x CPU + Slow 4G emulation
Instead of trusting the simulation, when you want to actually run and observe the page under genuinely throttled conditions, the place to go is the Chrome DevTools Performance panel. Per the official docs, the recommended flow is:
- Work in Incognito mode. Chrome's official guide says: "Incognito mode ensures Chrome runs in a clean state... extensions can introduce noise into performance measurements." This is how you keep installed extensions from polluting the trace.
- Turn on CPU throttling. In the Performance panel's recording settings, select "4x slowdown" for CPU. The docs are explicit: "Select 4x slowdown for CPU. DevTools throttles your CPU to run 4 times slower than normal."
- Set network throttling. Under Settings > Throttling you can use a ready-made preset (e.g., Slow 4G), or define your own custom throttling profile — you can calibrate download/upload Kbps, latency (ms), and packet loss to match real low-/mid-tier device scenarios.
- Leave the Screenshots option on. This is required to prove the LCP element in the next section.
- Start the recording, reload the page, stop the recording.
If you want to script these steps and make them repeatable, you can wire the same DevTools protocol into automation using Playwright's own trace API — turning manual clicks into a CI step.
1# Fetching field (CrUX) + lab (Lighthouse) data together from the PageSpeed Insights API2# PSI v5 public endpoint — returns lab + field data in one response3curl -sS "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://example.com&strategy=mobile" \4 | jq '{lab: .lighthouseResult.audits.metrics, field: .loadingExperience.metrics}'This command gives you both the simulated lab metrics and the CrUX field data in the same response — it's the fastest way to see the gap between the two.
I generally run this in two stages: first I check the field/lab gap from the PSI API to get a rough answer to "is this a real problem, or did the lab estimate inflate it?" — if the gap is small, there's no need to drop into DevTools at all. Only when it's large do I move to capturing a trace by hand in Incognito; taking a real trace for every page wastes time, since when the field/lab gap stays small, a real trace has usually shown me nothing new.
Verifying the LCP element with visual proof
The way to answer "is this the LCP element?" with a picture instead of words is scrubbing. The official DevTools guide describes it as: "Hover your mouse over the FPS, CPU, or NET charts. DevTools shows a screenshot of the page at that moment... This is called scrubbing." After capturing the recording, you can scrub back and forth along the timeline and watch, frame by frame, what the page was showing at each moment.
There's a precondition: the "Screenshots" checkbox needs to be on while recording — the docs state this as an explicit requirement. If it's off, you'll see empty frames when scrubbing and won't be able to visually verify the LCP element.
In practice: take the trace → go to the LCP marker → scrub to just before that point → visually confirm which element was last painted → select that node in Elements. This is where assumptions like "the LCP is always my hero image" can turn out wrong — a lazily loaded component or a late-rendering text block can be the real culprit.
Once I've found the node, I usually scrub once more in reverse, from the LCP moment back to the start of the recording, looking for the first frame where that element appears. The gap between first appearance and the LCP mark is what explains why it painted late — that's where you aim the optimization.
p75 vs average: which number you should trust
The official CWV methodology uses p75 (the 75th percentile), not the average — as quoted above, web.dev's definition is explicit: thresholds are evaluated "at the 75th percentile of page loads." The reason is simple: the average blends the extremes (very fast and very slow sessions) together and hides the real distribution; p75 is a threshold you can state as "the majority of your users did not have a worse experience than this."
The practical consequence: if a dashboard shows one number labeled only "average LCP," that's not the number used for CWV assessment. Check the p75 column separately — the gap can be large, especially if some traffic comes from low-end devices or weak networks. The average hides the extremes; p75 is the official metric.
I prefer keeping average and p75 side by side at all times: when the gap between them widens, that's usually the first sign of a problem in the tail of the distribution (a low-end device or weak-network segment) — p75 alone doesn't show how thick that tail is; you need to read both together.
Using Next.js 16.3's Instant Insights as a measurement tool
Next.js 16.3, released on August 3, 2026, adds a new layer to the measurement workflow: Instant Insights. The official announcement defines it as: "A new devtool that automatically surfaces slow navigations." It goes further: "With Instant Insights, we've turned slow navigations into an error in development." So now you can see, right during development, whether a given route transition is slow — without having to run Lighthouse and wait.
This behavior is opt-in via the cacheComponents: true flag (the Partial Prefetching behavior additionally requires partialPrefetching: true), and when a route awaits some data, it offers one of three options: Stream and Cache make the navigation instant, while Block deliberately ties it to the server.
1import { Suspense } from "react";2 3// 1) Stream: with Suspense — show a fallback until data arrives,4// the navigation is still counted as "instant" because the UI paints immediately5export default function ProductPage() {6 return (7 <Suspense fallback={<ProductSkeleton />}>8 <ProductDetails />9 </Suspense>10 );11}12 13// 2) Cache: with 'use cache' — cache the data at build/request time14async function getProduct(id: string) {15 "use cache";16 return db.product.findUnique({ where: { id } });17}18 19// 3) Block: deliberately opt out of "instant" behavior20export const instant = false;The Next.js team states outright that these behaviors "will become the default in a future major version" — not yet, but that's the direction. The practical benefit: Instant Insights tells you, during development, which route isn't counted as "instant" — so you already know which pages will be problematic before you ever run Lighthouse.
A regression gate in CI: testing navigation with instant()
The CI counterpart of Instant Insights is the instant() helper from the @next/playwright package. Adapted from the docs example:
1import { expect, test } from "@playwright/test";2import { instant } from "@next/playwright";3 4test("product title is available immediately", async ({ page }) => {5 await page.goto("/products/shoes");6 7 // Verify what's visible without waiting for the network8 await instant(page, async () => {9 await page.click('a[href="/products/hats"]');10 await expect(page.locator("h1")).toContainText("Baseball Cap");11 await expect(page.getByText("Checking inventory...")).toBeVisible();12 });13 14 await expect(page.getByText("12 in stock")).toBeVisible();15});instant() restricts the assertions inside the block to "UI that must be instantly visible on navigation" — the test verifies what must paint immediately without waiting on a network request. If a refactor slows the navigation and delays this UI, the test goes red; it's a concrete tool that carries CWV's lab-testing layer directly into CI.
The Next.js team is exploring a step further: "We're exploring ways to surface the errors you see in development during the build process too, so regressions in instant routes can be caught at build time." Not finalized yet — read it as a planned direction.
Where you put instant() tests matters: focus on critical user-flow points (main navigation, checkout steps, search-result clicks), not every page. A test per route increases maintenance and drowns the signal in noise; like CWV itself, a few measurements in the right places beat many shallow ones.
What to keep server-side LHCI/PSI for, and what not to
The practical rule is: server-side LHCI/PSI running is for trend, definitive proof needs a real Chrome trace. There's technical backing for this in the official docs too: "Simulated throttling continues to be the default setting. This matches the default setup of PageSpeed Insights and Lighthouse CLI, and preserves consistency across tools."
So the daily PSI/LHCI run gives you a comparable trend line — you can confidently read a signal like "2.1s one day, 2.4s the next" because the same engine runs with the same assumptions every time. But deterministic simulation can't answer "why did it go up to 2.4s?" — for that you need a real trace captured with 4x CPU + Slow 4G, and an LCP element verified through scrubbing.
Which tool answers which question
Question | Right tool | Why |
|---|---|---|
My Lighthouse score is low — is it low in the field too? | CrUX (PSI field data / Search Console) | p75 of real users, not an estimate |
Which node is actually the LCP element? | DevTools Performance + scrubbing | Provides screenshot proof |
Did a route get slower after a refactor? | @next/playwright instant() | Catches navigation regressions in CI |
What's the daily trend — are we regressing? | LHCI / PSI (Simulated throttling) | Deterministic, consistent across tools |
If you want to set up a budget gate in CI, you can use Lighthouse CI's own assertion configuration; you'll need to fill in the numbers against your own p75 targets — the values shown here are a template, not a measured result:
1{2 "ci": {3 "assert": {4 "assertions": {5 "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],6 "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],7 "interaction-to-next-paint": ["warn", { "maxNumericValue": 200 }]8 }9 }10 }11}These thresholds match web.dev's official "good" boundaries (2.5s / 0.1 / 200ms) — you're tying your CI gate directly to CWV's own definition.
GOLDEN TIP
The most valuable insight in this article
This tip holds the article's most important takeaway.
Easter Egg
You found a hidden gem!
There's a hidden detail in this section. Want to uncover it?
Reader Reward
If reading this made you want to set up a real CWV measurement routine on your own project, apply the checklist below in order; each item corresponds to one of the sections above and is a meaningful step on its own.
FAQ
Why does my Lighthouse score differ from real user data?
Because Lighthouse simulates a single synthetic session under a fixed scenario, while CrUX is p75 data accumulated from real users over days. Device diversity, variable networks, and real third-party script behavior separate the lab result from the field. Neither is "wrong" — they answer different questions.
How do I measure LCP with a real Chrome trace?
Instead of trusting Lighthouse's simulation, take a recording in the DevTools Performance panel in Incognito mode, with CPU throttling set to 4x slowdown, the network set to Slow 4G (or your own calibrated custom profile), and Screenshots turned on. After the recording, scrub to the LCP marker to visually verify the element.
What is simulated throttling, and why can it deviate from real measurement?
Simulated throttling is Lighthouse's default method: it simulates a load from data observed during an initial unthrottled load, without actually slowing the page. It's roughly as accurate as DevTools throttling on most sites but can drift on edge cases. The comparison point isn't flawless either: it's DevTools throttling that applies delay at the request level rather than the packet level as on real mobile networks, so comparing two lab results is no substitute for field reality.
How do I catch CWV regressions in CI?
Add the instant() helper from the @next/playwright package, introduced with Next.js 16.3, to your critical routes; it tests the UI that must be instantly visible on navigation without waiting for a network request. Also, in development, Instant Insights automatically turns slow navigations into an error — so you catch the regression before it's ever committed.
Is "Slow 4G" in Lighthouse a real 4G connection?
No. According to the official docs, this preset represents "the slowest 25% of 4G connections combined with the fastest 25% of 3G connections," and it used to be labeled "Fast 3G." The name changed but the constraint level stayed the same; don't trust the name itself and assume you're running a real 4G test.
Why does the difference between p75 and the average matter?
The official CWV assessment uses p75, not the average, because the average blends the extremes together and hides the real distribution. When you see "average LCP" on a dashboard, that's not the number used in the site's CWV assessment — you need to check the p75 column separately.
Conclusion
If your Lighthouse score and your field data don't agree, first clarify which layer you're actually talking about: lab, field (CrUX), or a real trace? Simulated throttling is valuable for fast feedback in CI, but root cause analysis needs a real DevTools trace captured with 4x CPU + Slow 4G and an LCP element verified through scrubbing. Next.js 16.3's Instant Insights and @next/playwright's instant() helper carry this workflow into development and CI; keep LHCI/PSI for daily trend, and save definitive proof for a real trace.
For a deeper look at caching and navigation behavior on the Next.js side, see the Next.js 16.3 Instant Navigations and Cache Components Guide; for the runtime-side changes, Next.js 16.3 Drops runtime='edge': The Return to Node is a good companion. For the same profiling methodology on a different platform, iOS Performance Profiling with Instruments: The Complete Guide covers a similar "lab vs. real measurement" distinction on iOS; for continuous monitoring, see the iOS Performance Monitoring & Observability Guide. If you're chasing 60fps on Flutter, Flutter Performance Optimization: The 60fps Guarantee Guide applies the same "measure, then optimize" discipline.
Sources
- Core Web Vitals — web.dev — official definition of LCP/INP/CLS thresholds and the p75 methodology
- Lighthouse Throttling — GoogleChrome/lighthouse — simulated throttling, Slow 4G/Fast 3G preset history, packet vs. request-level delay
- Chrome DevTools Performance panel documentation — 4x CPU throttling, Incognito recommendation, scrubbing and the Screenshots setting
- Chrome DevTools Throttling settings — custom network throttling profiles (Kbps, latency, packet loss)
- Next.js 16.3 announcement — Instant Insights and the future default-behavior plan
- Next.js Instant Navigations guide — Stream/Cache/Block models, the
@next/playwrightinstant()example, and the build-time investigation
Tags
iOS Development News
Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.
Your subscription starts when you open the link in the confirmation email and press “Confirm my subscription”. The newsletter keeps open/click statistics; you can unsubscribe anytime with one click. Privacy
