Monitoring Core Web Vitals over time: field data lags 28 days
Search Console's Core Web Vitals report uses 28 days of field data, so a regression shows slowly. Add real-user monitoring and lab checks to see it sooner.
LLaunchScaler·Published ·8 min read
Search Console's Core Web Vitals report is built on real-user data covering the last 28 days, measured at the 75th percentile, so a regression you ship today appears gradually over the following weeks as new page loads replace old ones. To see a regression sooner, measure the metrics yourself with the web-vitals library for day-level data, and run lab checks at every deploy and on a schedule.
Google's field data stays the reference for how your pages are judged. Your own monitoring exists to see the change before that reference moves.
Why does Core Web Vitals field data lag behind your site?
Every public source of Core Web Vitals field data reads from the Chrome UX Report, and CrUX is "a 28-day rolling average of aggregated metrics." Each daily value mixes the newest page loads with up to four weeks of older ones. A change shows up only as fast as it can shift the 75th percentile of that whole window.
Search Console states the window per metric. For LCP, the group value means "75% of page requests took this amount of time or less to reach largest contentful paint in the last 28 days," and INP and CLS are described the same way. A URL group's status "defaults to the slowest status assigned to it," so one failing metric fails the group.
The 75th percentile makes the lag easy to estimate. A metric fails once more than 25% of the page loads in the window are slow. If traffic is steady and a release makes every new page load slow, the combined window crosses that line after about 7 of its 28 days. If the release makes half of new loads slow, it takes about 14 days. A regression that hits a quarter of loads or fewer may never cross it, and still costs you real users.
The fix path lags the same way. Search Console's validation "(re)starts the clock on a 4-week monitoring period," and "if this issue is not present in any URLs on your site during the 28-day window, the issue is considered fixed." The full explanation of a failing assessment is in Core Web Vitals assessment failed.
Where can you see Core Web Vitals data, and how fresh is each source?
Google offers several views of the same field data, each with a different update cadence, and lab tools that measure a single simulated load right now. Use field sources to judge the result and lab sources to catch the cause. The table shows what each one reports and how often it changes.
Questions, answered
What people ask about this
01
Why is Core Web Vitals data delayed?
Search Console, PageSpeed Insights and the CrUX API report real-user data aggregated over a rolling 28-day window, at the 75th percentile. A change you ship today is mixed with up to 27 days of older page loads, so it shows up gradually rather than the next day.
PageSpeed Insights explains the fallback: when a page lacks enough real-user samples, "PSI will fall back to origin-level granularity." On a small site, many pages only ever show origin data, which is one more reason to collect your own.
How do you get day-level field data with the web-vitals library?
Load Google's web-vitals library on your pages, send each metric to your own endpoint, and compute the 75th percentile per day and per page template. The library is small ("~3K, brotli'd") and reports the same metrics CrUX does, from your real visitors, on every page view.
Install it: npm install web-vitals.
Report each metric to an endpoint you control, using navigator.sendBeacon, which the library's documentation notes "supports sending data as the page is being unloaded."
Store the metric name, value, page path or template, device type and date.
Each day, compute the 75th percentile per metric and template, separately for mobile and desktop.
Alert when a daily value crosses the "good" threshold two days in a row, so a single noisy day does not page anyone.
import { onCLS, onINP, onLCP } from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
page: location.pathname,
});
navigator.sendBeacon('/analytics', body);
}
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
Your numbers will not match CrUX exactly. CrUX covers Chrome only, and only pages that are "publicly discoverable" and "sufficiently popular," while your script sees every browser that runs it. Use your own series to spot change, and CrUX for the level Google judges.
How do you catch a speed regression at deploy time?
Run a lab test on your key templates for every pull request or deploy, with budgets that fail the build. Lab data comes from "a simulated load of a page on a single device and fixed set of network conditions," so it is repeatable enough to compare one release with the next, days before any field data moves.
Lighthouse CI is built for this. Google describes it as a way to "get a Lighthouse report alongside every PR" and to "prevent regressions" with performance budgets. Install it with npm install -g @lhci/cli and run lhci autorun in your pipeline, with assertions on LCP, CLS and Total Blocking Time.
INP needs real interactions, so a standard page-load lab test cannot measure it. Use Total Blocking Time as the stand-in: web.dev calls TBT "a reasonable proxy metric for INP for the lab" while warning that it is "not a substitute for INP in and of itself." A jump in TBT after a release, such as a new chat widget or tag manager container, is the early sign of an INP regression.
Deploy-time tests only cover pages and conditions you configured. A scheduled lab check on production, every two weeks or more often, catches changes that arrive without a deploy, such as a third-party script updating itself. Tools that watch for changes between runs are compared in website change monitoring tools.
How do you track the CrUX trend over time?
Query the CrUX History API for your origin and key URLs, and chart the weekly 75th percentile for each metric. It returns 25 weekly collection periods by default, "a number between 1 and 40" if you ask, so you get about six months of history by default, and about nine months at the maximum, without storing anything yourself.
The History API "is updated each Monday around 04:00 UTC and contains data up until the previous Saturday." Each point is itself a 28-day window, so consecutive weeks overlap heavily; read the direction of the line rather than week-to-week noise. For a single current value per day, use the regular CrUX API at records:queryRecord with the same body. Both need a Google Cloud API key provisioned for the Chrome UX Report API.
What should you do when a regression shows up?
Date it first, then find the change that shipped that day. With your own daily data the start date is visible to the day; with CrUX alone it is smeared across weeks, which is the main reason to collect your own.
Find the first day your daily 75th percentile crossed the threshold, per template and device.
Match that day against your deploy log, tag manager publishes and third-party script updates.
Reproduce it in the lab on one URL from the affected template, before and after the suspect change.
Ship the fix and confirm the daily field value returns below the threshold within a few days.
In Search Console, open the issue and start validation. Expect it to take the full 28-day window, because the report has to see a clean month of data.
What thresholds should trigger a Core Web Vitals alert?
Alert on the same thresholds Google uses, applied to your own daily data, and set lab budgets a little tighter so a regression fails in CI before it can reach users. The field thresholds come from Google's report; the lab budgets are yours to tune against your current baseline.
Metric
Good (p75)
Poor (p75)
Alert on your daily field data
Lab budget idea
LCP
2.5 s or less
Over 4 s
Two days over 2.5 s
10% under your current lab LCP
INP
200 ms or less
Over 500 ms
Two days over 200 ms
Total Blocking Time held at its baseline
CLS
0.1 or less
Over 0.25
Two days over 0.1
Stay at or below 0.1
Segment every alert by mobile and desktop and by template. A regression on the pricing page on mobile is a different fix from a sitewide font change, and an average across everything hides both. Where speed sits in the wider monitoring schedule is covered in SEO monitoring for a small site.
Get the field and lab picture every two weeks
Real-user monitoring and CI budgets need code and upkeep. LaunchScaler Watch adds a scheduled outside view for one website at $29/mo: its re-scan every two weeks reads your field LCP, INP and CLS at the 75th percentile where Google has field data for your site, alongside lab checks such as Total Blocking Time and render-blocking resources, emails a diff on every run with the evidence behind each change, runs uptime checks on your health endpoint in between, and alerts you when a check drops below the bar. Start watching.
Measure them on your own pages with Google's web-vitals JavaScript library, send each value to your analytics endpoint, and compute the 75th percentile per day and per template. Add lab tests with Lighthouse CI to catch regressions before they reach users.
03
How often is the Core Web Vitals report updated?
The CrUX API behind the field data is updated daily around 04:00 UTC, each value covering the previous 28 days. The CrUX History API updates each Monday and returns 25 weekly collection periods by default, up to 40.
04
What are the Core Web Vitals thresholds?
Good means LCP of 2.5 seconds or less, INP of 200 milliseconds or less and CLS of 0.1 or less, measured at the 75th percentile of page loads. Poor starts above 4 seconds for LCP, 500 milliseconds for INP and 0.25 for CLS.
05
How long does Core Web Vitals validation take in Search Console?
Start tracking begins a 28-day monitoring period of real-user data. If the issue is not present in any URL during that window, Search Console considers it fixed.
When Google organic traffic drops, check tracking, then indexing, then rankings, then AI Overviews and seasonality. The order, the reports and the tells.