Web Vitals: Lab Data vs. Field Data
A high Lighthouse score does not guarantee a fast user experience. This article explains the gap between simulated lab data and real-user field data, why field results are authoritative, how to collect them with CrUX, the web-vitals library, and RUM, and when to use each data source.
Key Points
- Topic Focus: Next.js、React
- Suitable for: indie hackers, full-stack engineers, AI application developers
- Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
Anyone who has worked on performance optimization has probably encountered this scenario: you run Lighthouse locally, score 98, see green across every metric, and feel pretty good about it. Then, a week after launch, users complain that "the page is laggy" or "it won't open," and your boss confronts you with screenshots. You check monitoring and discover that real users' median LCP is three times your local result.
Your measurement was not wrong. You simply were not measuring the same thing.
Web Vitals has two data sources: Lab Data and Field Data. They measure completely different things. Ignore that distinction, and your performance optimization efforts may keep going in circles.
1. Lab Data: An "Ideal Result" Produced in a Clean Environment
As the name suggests, lab data is collected in a controlled, simulated environment. The primary tools are Lighthouse (the one built into Chrome DevTools, used by PageSpeed Insights, and run in CI) and WebPageTest.
It works roughly like this:
- A machine with known specifications (usually a fixed-spec server in a data center or a CI runner);
- A simulated network throttling profile (for example, 4G = 9Mbps bandwidth / 170ms RTT);
- Simulated CPU slowdown (4x throttling);
- A brand-new browser session with an empty cache;
- The page is loaded and a set of predefined interactions is performed (or no interaction occurs and only loading is measured);
- The tool produces a complete set of metrics and scores.
Its core advantage is reproducibility: with the same commit and configuration, today's and tomorrow's results should be nearly identical. That means you can use it for regression comparisons: "Did this change reduce LCP from 2.1s to 1.8s?" The answer is unambiguous.
But its fatal weakness is precisely that "controlled environment." A simulated environment is never the real environment.
2. Field Data: The "Truth" Collected from Real Users
Field data, also called RUM (Real User Monitoring), is reported by the browsers of real users visiting your website.
During every real visit, the user's browser measures metrics such as LCP, INP, and CLS, then uses a small piece of JS (Beacon / fetch) to send those values, along with dimensions such as device type, network conditions, and page path, to your server or to Google.
Field data reflects the real distribution. Some users have an iPhone 15 Pro and a gigabit home fiber connection. Others have a three-year-old budget Android phone and an unstable 4G signal. Still others have 50 tabs open, eight ad-blocking extensions installed, and a system update downloading in the background. Every user's experience is one data point, and only the aggregate reveals how your website actually performs "in the wild."
Lab data tells you "how fast this page can be"; field data tells you "how fast this page actually is."
3. Why the Gap Can Be Enormous
Many teams are shocked the first time they seriously compare lab and field data. The gap is often 2-5x. The reasons are straightforward:
1. Users' devices are much slower than your Mac. You run Lighthouse on an M2 MacBook Pro, and even after 4x CPU throttling, it may still be faster than the CPU in a midrange Android phone. On a real user's low-end device, running React hydration may take 1.5s, while it takes only 200ms locally.
2. Users' networks are much slower than your fiber connection. Simulated 4G is "ideal 4G": a stable 9Mbps and 170ms RTT. Real-world 4G can suddenly drop to 1Mbps, spike to 400ms RTT, and disconnect and reconnect in the subway. You may receive the first byte in 50ms, while a user waits 800ms.
3. You cannot simulate the state of a user's browser. Cache hits, Service Worker interception, resource contention from background tabs, scripts injected by extensions, actual screen dimensions and DPR, CPU throttling in battery-saver mode: Lighthouse cannot simulate any of them.
4. You cannot predict user behavior. Lighthouse effectively measures "load and stop," but real users scroll immediately, click buttons right away, and interact aggressively before the page is ready. All of this affects INP (interaction latency), which your lab test may not measure at all.
This leads to a hard rule: if an optimization improves only lab data while field data does not move, you may be optimizing a "simulated problem" rather than a real user problem.
4. Google Rankings Use Field Data, Not Lighthouse Scores
Many people get this wrong. On PageSpeed Insights, you will see two tabs: Lab Data and Field Data. The field data comes from CrUX (Chrome User Experience Report), Google's domain-aggregated performance data collected from real Chrome users.
The "Page Experience signal" used by Google Search rankings is based on CrUX field data, not Lighthouse scores. You can score 100 in Lighthouse, but if your mobile p75 LCP is 4 seconds in CrUX, your ranking can still be penalized.
CrUX has a coverage threshold: a site must have enough real traffic before Google collects and publishes its data. If a small or medium-sized site does not have sufficient traffic, PageSpeed Insights will show "Field data unavailable." In that case, you must build your own RUM, or you will have no idea what real users experience.
5. How to Collect Field Data
There are three approaches, from lightweight to comprehensive:
1. The web-vitals library (strongly recommended as the foundation of your own RUM)
Google's officially maintained web-vitals library lets you obtain LCP/INP/CLS/TTFB/FCP with just a few lines of browser-side code, then report the data wherever you want.
// src/app/web-vitals.ts
import { onCLS, onINP, onLCP, onTTFB, onFCP } from 'web-vitals';
function sendToAnalytics(metric: { name: string; value: number; id: string; rating: string }) {
// Use sendBeacon so the data can still be sent when the page unloads
const body = JSON.stringify({
name: metric.name, // 'LCP' | 'INP' | 'CLS' | ...
value: metric.value, // Numeric value, such as LCP in milliseconds
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
id: metric.id, // Unique ID for this session, useful for deduplication
page: location.pathname,
// Dimensions: add device / network / source as needed
device: /Mobi|Android/i.test(navigator.userAgent) ? 'mobile' : 'desktop',
referrer: document.referrer,
});
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/vitals', body);
} else {
fetch('/api/vitals', { body, method: 'POST', keepalive: true });
}
}
export function reportWebVitals() {
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);
onTTFB(sendToAnalytics);
onFCP(sendToAnalytics);
}
// src/app/layout.tsx(Client Component)
'use client';
import { useEffect } from 'react';
import { reportWebVitals } from '@/app/web-vitals';
export function WebVitalsBootstrap() {
useEffect(() => { reportWebVitals(); }, []);
return null;
}
Note the use of sendBeacon / fetch(..., keepalive: true): the final INP and CLS values are often not known until the user leaves the page. You must use an API that can send data during unload, or those measurements will be lost.
2. Send It to an Existing Backend
After the /api/vitals reporting endpoint receives the data, you can:
- Forward it to Google Analytics 4 (using
gtag('event', 'web_vitals', {...})): free and suitable for personal sites; - Send it to Sentry / Datadog / New Relic: these APM platforms all provide RUM modules with built-in dashboards and alerts;
- Store it in your own database table (for example, Postgres plus a simple aggregation query) for complete control.
3. CrUX (Passive Collection)
If your site has enough traffic, Google collects the data automatically, and you can view the results directly in PageSpeed Insights or the "Core Web Vitals" report in Search Console. There is no operational overhead, but you can only see aggregated domain-level data and cannot drill down into individual pages or user groups (unless you use the CrUX API/CrUX Dashboard, which still provides monthly aggregates).
Practical recommendation: install the web-vitals library for your own RUM and use CrUX for cross-validation. The former is real-time and supports drill-down analysis; the latter is authoritative and supports competitor comparisons.
6. Analyzing Field Data: Look at p75, Not the Average
Once you have a collection of field data, do not look at the average. Performance data has an extremely long-tailed distribution (99 users at 1 second plus one user at 30 seconds gives an average of 1.29 seconds, but the user who waited 30 seconds has already left). Averages conceal the real problem.
The industry standard is p75 (the 75th percentile): sort all samples from smallest to largest and take the value at the 75% position. It means that "75% of users have an experience at least this good." Google CrUX and every Web Vitals pass/fail assessment use p75.
You should also always segment the data:
- By device: "What is mobile p75 LCP? What about desktop?" The answers often differ by 2-3x.
- By network: "What is p75 INP for 4G users? What about slow 3G?"
- By page: "Which has worse CLS, the product detail page or the homepage?"
- By route/entry source: "Do users arriving from search and social media have the same experience?"
A practical rule of thumb: "overall p75 passes" is meaningless; "mobile p75 LCP passes" is what actually matters. The worst experience always belongs to users on mobile devices and slow networks. Isolate that group to find the real problem.
7. The Right Use for Each Type of Data
Do not treat them as opposing approaches. They are complementary:
| Scenario | Use | Why |
|---|---|---|
| Local development and immediate validation after a code change | Lab data (Lighthouse) | Reproducible, fast feedback, and no need to wait for user visits |
| CI performance regression gates | Lab data | Run it on every PR; stable numbers allow you to define thresholds |
| Assessing whether production actually meets its targets | Field data (RUM / CrUX) | Reflects real experience, and Google rankings use it too |
| Finding experience bottlenecks for specific pages or user groups | Field data | Only field data can drill down by device and network |
| Comparing competitors and industry rankings | CrUX | A highly comparable public dataset |
In short: lab data governs the "development loop"; field data governs "business decisions."
8. Do Not Optimize Only for Lighthouse Scores
This is a real trap. I have seen teams make a pile of "simulation-specific" optimizations to push Lighthouse from 90 to 100: reducing fonts, lazy-loading everything, preconnecting to a long list of domains, and so on. Lighthouse did reach 100, but the field data barely moved. Those optimizations targeted bottlenecks amplified by simulated throttling, while real users were blocked elsewhere, perhaps by a third-party SDK, a long main-thread task, or hydration on a low-end device.
Worse, some "score-chasing" tactics actually harm the real experience. For example, aggressively delaying the loading of above-the-fold images may save resources on a fast network and capable device, but it can make LCP occur even later on weaker devices.
So here is the final hard rule:
Lighthouse is the compass during development; CrUX/RUM is the judge in production. Optimize for field data and use lab data as supporting evidence.
The next time you want to squeeze another two points out of Lighthouse, open your RUM dashboard first and ask: "Will this change reduce mobile p75 LCP?" If the answer is "I don't know," hold off on that optimization.
Summary
- Lab data (Lighthouse) is measured in a simulated, clean, reproducible environment and is suitable for development debugging and CI regressions. Field data (RUM/CrUX) comes from real users, reflects actual experience, and is the final measure of production quality.
- The gap comes from real-world variables: slow devices, poor networks, extensions, caches, and user behavior. Labs cannot simulate them all, so field results are usually 2-5x worse than lab results.
- Google rankings use CrUX field data, not Lighthouse scores. Sites without enough traffic to qualify for CrUX must build their own RUM.
- Collect field data with the
web-vitalslibrary and report it usingsendBeacon. Analyze it with p75 and segmentation by device/network/page. "Mobile p75" is the meaningful standard. - Do not optimize only for Lighthouse scores. You may improve the simulated environment without changing the field experience at all. Treat field data as authoritative and lab data as a development aid.
Key takeaway: Lab data tells you "how fast your page can run"; field data tells you "how slow your users actually experience it." In performance optimization, the latter is the battle you ultimately need to win.
Related
Related Content
Article
Is Next.js App Router a Good Fit for a Technical Community?
Technical trends, engineering practices, and independent development perspectives centered on Next.js and React.
Article
SEO Design for a Content Community with Next.js
Technical trends, engineering practices, and an indie developer's perspective on Next.js and TypeScript.
Article
How Should a Technical Community Design Its Tag System and Recommendation Feed?
Centered on independent development, Next.js technology trends, engineering practices, and the indie developer perspective.
Article
The Engineering Value of TypeScript in AI Application Frontends
Technical trends, engineering practices, and an independent developer perspective on TypeScript and React.