Lab paint timeline
Lab paint timeline on a cold crawl: TTFB, FCP, LCP in ms - setup-specific, not field data.
At a glance the headline numbers for Lab paint timeline
Lab paint timeline on a cold crawl: TTFB, FCP, LCP in ms - setup-specific, not field data.
In the lab, the typical site paints its main content after 556ms.
Distribution & median CLS site count and median CLS at each level of lab paint timeline — ttfb
Passing CLS by lab paint timeline — ttfb which level passes the CLS most often
Lab paint timeline — ttfb 114ms. p75 399ms. p99 3.4s. At the low end (0–99ms): CLS 0.00. At the high end (1000+ms): CLS 0.00. computed
Distribution & median CLS site count and median CLS at each level of lab paint timeline — fcp
Passing CLS by lab paint timeline — fcp which level passes the CLS most often
Lab paint timeline — fcp 366ms. p75 776ms. p99 10.2s. At the low end (0–249ms): CLS 0.00. At the high end (1750+ms): CLS 0.00. computed
Distribution & median CLS site count and median CLS at each level of lab paint timeline — lcp
Passing CLS by lab paint timeline — lcp which level passes the CLS most often
Lab paint timeline — lcp 556ms. p75 1.4s. p99 19.3s. At the low end (0–499ms): CLS 0.00. At the high end (4500+ms): CLS 0.00. computed
Why this matters for the Core Web Vitals, and where to start fixing it
These are lab numbers from our crawler: one cold load, one machine, one network profile. They are not what your visitors feel. That is what the field data on the rest of this site measures. What the lab gives you is a controlled comparison: identical conditions for every site, so a difference between two sites is a real difference between the pages.
Read the gaps. Lab LCP far worse than field LCP usually means caching and repeat visits carry the field numbers. The reverse usually means the crawler's connection is kinder than your visitors' phones.
How does this affect the Core Web Vitals?
Lab paint timeline correlates with the CLS. Lab LCP separates passing sites from failing sites the most. Where the lab LCP is low, 91% of sites pass the CLS. Where it is high, 86% do. The decline is gradual. There is no point where sites suddenly start failing.
Chrome field data from 189,915 sites, representing millions of real page loads. How we measured.
Live queries (6) — admin only
SELECT COUNT(*) AS count,
quantile_disc(m.synthetic.paint.ttfb, 0.10) AS p10,
quantile_disc(m.synthetic.paint.ttfb, 0.25) AS p25,
quantile_disc(m.synthetic.paint.ttfb, 0.50) AS p50,
quantile_disc(m.synthetic.paint.ttfb, 0.75) AS p75,
quantile_disc(m.synthetic.paint.ttfb, 0.90) AS p90,
quantile_disc(m.synthetic.paint.ttfb, 0.99) AS p99
FROM sites WHERE m.synthetic.paint.ttfb IS NOT NULL;
SELECT CASE WHEN m.synthetic.paint.ttfb >= 0 AND m.synthetic.paint.ttfb < 100 THEN 0 WHEN m.synthetic.paint.ttfb >= 100 AND m.synthetic.paint.ttfb < 200 THEN 1 WHEN m.synthetic.paint.ttfb >= 200 AND m.synthetic.paint.ttfb < 300 THEN 2 WHEN m.synthetic.paint.ttfb >= 300 AND m.synthetic.paint.ttfb < 400 THEN 3 WHEN m.synthetic.paint.ttfb >= 400 AND m.synthetic.paint.ttfb < 500 THEN 4 WHEN m.synthetic.paint.ttfb >= 500 AND m.synthetic.paint.ttfb < 600 THEN 5 WHEN m.synthetic.paint.ttfb >= 600 AND m.synthetic.paint.ttfb < 700 THEN 6 WHEN m.synthetic.paint.ttfb >= 700 AND m.synthetic.paint.ttfb < 800 THEN 7 WHEN m.synthetic.paint.ttfb >= 800 AND m.synthetic.paint.ttfb < 900 THEN 8 WHEN m.synthetic.paint.ttfb >= 900 AND m.synthetic.paint.ttfb < 1000 THEN 9 WHEN m.synthetic.paint.ttfb >= 1000 THEN 10 END AS bucket_idx, COUNT(*) AS n,
quantile_disc(crux."phone".cls, 0.5) AS median,
COUNT(*) FILTER (WHERE crux."phone".cls IS NOT NULL AND crux."phone".cls <= 0.1) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE crux."phone".cls IS NOT NULL), 0) AS good_pct
FROM sites WHERE m.synthetic.paint.ttfb IS NOT NULL GROUP BY bucket_idx;
SELECT COUNT(*) AS count,
quantile_disc(m.synthetic.paint.fcp, 0.10) AS p10,
quantile_disc(m.synthetic.paint.fcp, 0.25) AS p25,
quantile_disc(m.synthetic.paint.fcp, 0.50) AS p50,
quantile_disc(m.synthetic.paint.fcp, 0.75) AS p75,
quantile_disc(m.synthetic.paint.fcp, 0.90) AS p90,
quantile_disc(m.synthetic.paint.fcp, 0.99) AS p99
FROM sites WHERE m.synthetic.paint.fcp IS NOT NULL;
SELECT CASE WHEN m.synthetic.paint.fcp >= 0 AND m.synthetic.paint.fcp < 250 THEN 0 WHEN m.synthetic.paint.fcp >= 250 AND m.synthetic.paint.fcp < 500 THEN 1 WHEN m.synthetic.paint.fcp >= 500 AND m.synthetic.paint.fcp < 750 THEN 2 WHEN m.synthetic.paint.fcp >= 750 AND m.synthetic.paint.fcp < 1000 THEN 3 WHEN m.synthetic.paint.fcp >= 1000 AND m.synthetic.paint.fcp < 1250 THEN 4 WHEN m.synthetic.paint.fcp >= 1250 AND m.synthetic.paint.fcp < 1500 THEN 5 WHEN m.synthetic.paint.fcp >= 1500 AND m.synthetic.paint.fcp < 1750 THEN 6 WHEN m.synthetic.paint.fcp >= 1750 THEN 7 END AS bucket_idx, COUNT(*) AS n,
quantile_disc(crux."phone".cls, 0.5) AS median,
COUNT(*) FILTER (WHERE crux."phone".cls IS NOT NULL AND crux."phone".cls <= 0.1) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE crux."phone".cls IS NOT NULL), 0) AS good_pct
FROM sites WHERE m.synthetic.paint.fcp IS NOT NULL GROUP BY bucket_idx;
SELECT COUNT(*) AS count,
quantile_disc(m.synthetic.paint.lcp, 0.10) AS p10,
quantile_disc(m.synthetic.paint.lcp, 0.25) AS p25,
quantile_disc(m.synthetic.paint.lcp, 0.50) AS p50,
quantile_disc(m.synthetic.paint.lcp, 0.75) AS p75,
quantile_disc(m.synthetic.paint.lcp, 0.90) AS p90,
quantile_disc(m.synthetic.paint.lcp, 0.99) AS p99
FROM sites WHERE m.synthetic.paint.lcp IS NOT NULL;
SELECT CASE WHEN m.synthetic.paint.lcp >= 0 AND m.synthetic.paint.lcp < 500 THEN 0 WHEN m.synthetic.paint.lcp >= 500 AND m.synthetic.paint.lcp < 1000 THEN 1 WHEN m.synthetic.paint.lcp >= 1000 AND m.synthetic.paint.lcp < 1500 THEN 2 WHEN m.synthetic.paint.lcp >= 1500 AND m.synthetic.paint.lcp < 2000 THEN 3 WHEN m.synthetic.paint.lcp >= 2000 AND m.synthetic.paint.lcp < 2500 THEN 4 WHEN m.synthetic.paint.lcp >= 2500 AND m.synthetic.paint.lcp < 3000 THEN 5 WHEN m.synthetic.paint.lcp >= 3000 AND m.synthetic.paint.lcp < 3500 THEN 6 WHEN m.synthetic.paint.lcp >= 3500 AND m.synthetic.paint.lcp < 4000 THEN 7 WHEN m.synthetic.paint.lcp >= 4000 AND m.synthetic.paint.lcp < 4500 THEN 8 WHEN m.synthetic.paint.lcp >= 4500 THEN 9 END AS bucket_idx, COUNT(*) AS n,
quantile_disc(crux."phone".cls, 0.5) AS median,
COUNT(*) FILTER (WHERE crux."phone".cls IS NOT NULL AND crux."phone".cls <= 0.1) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE crux."phone".cls IS NOT NULL), 0) AS good_pct
FROM sites WHERE m.synthetic.paint.lcp IS NOT NULL GROUP BY bucket_idx;