A/B testing impact

Performance impact by ab_testing provider.

Field data PhoneDesktopAll Scope All sites Q2 2026 edition · All devices field outcomes
Metric LCP INP CLS
1

At a glance the headline numbers for A/B testing impact

Performance impact by ab_testing provider.

8
Providers ranked
After min-sites filter
21,139
Sites in sample
Combined across all items
2.2s
Best LCP (p75)
Google Optimize
The State of Web Vitals · Q2 2026 · 189,915 sites · all devices field datacorewebvitals.io/state-of-cwv
2

The ranking sorted by LCP at p75, fastest first

# Provider LCP p75 Passing Sites
1 Google Optimize 2.2s 82.2%
19,960
2 AB Tasty 2.3s 77.9%
109
3 VWO 2.4s 77.7%
433
4 Kameleoon 2.4s 77.3%
112
5 Dynamic Yield 2.6s 69.8%
65
6 Convert 2.7s 69.9%
192
7 Optimizely 2.8s 69.4%
137
8 LaunchDarkly 3.9s 48.8%
131
The State of Web Vitals · Q2 2026 · 189,915 sites · all devices field datacorewebvitals.io/state-of-cwv
3

Passing LCP per provider which group passes the LCP most often

ProviderSitesPassing LCPp75
Statsig 0% 2 100% 2.1s
Monetate 0% 26 88% 2.3s
Google Optimize 10.5% 19,960 82% 2.2s
AB Tasty 0.1% 109 78% 2.3s
VWO 0.2% 433 78% 2.4s
Kameleoon 0.1% 112 77% 2.4s
Convert 0.1% 192 70% 2.7s
Dynamic Yield 0% 65 70% 2.6s
Optimizely 0.1% 137 69% 2.8s
LaunchDarkly 0.1% 131 49% 3.9s
Split.io 0% 9 33% 3.3s
Evolv ai 0% 1 2.8s
Good Needs Improvement Poor Sorted best-passing first · median colored by its own rating · pass = good LCP (2.5s at p75) · one value per site

82% of Google Optimize sites pass LCP. LaunchDarkly trails 33 points behind, leaving 51% of its sites failing. computed

The State of Web Vitals · Q2 2026 · 189,915 sites · all devices field datacorewebvitals.io/state-of-cwv
4

All five vitals at once the whole category without toggling - cell is the pass rate, small number the p75

LCP
INP
CLS
FCP
TTFB
VWO 0.2%
782.4s
80179ms
840.05
721.9s
551.2s
AB Tasty 0.1%
782.3s
86169ms
720.12
711.9s
61988ms
Statsig 0%
1002.1s
50362ms
1000.02
1001.8s
100514ms
Evolv ai 0%
02.8s
0444ms
00.27
02.4s
01.7s
Google Optimize 10.5%
822.2s
96104ms
860.05
751.8s
551.3s
Convert 0.1%
702.7s
86181ms
800.08
751.8s
561.4s
Kameleoon 0.1%
772.4s
71211ms
710.12
721.9s
531.1s
Dynamic Yield 0%
702.6s
47295ms
630.19
751.8s
61967ms
LaunchDarkly 0.1%
493.9s
76197ms
730.11
492.7s
551.2s
Split.io 0%
333.3s
63226ms
440.34
442.6s
56884ms
Optimizely 0.1%
692.8s
73205ms
800.09
642.2s
461.3s
Monetate 0%
882.3s
71213ms
770.08
771.7s
381.0s
60%95%+ passing Cell: pass rate, small number = p75 · faded rows: under 100 sites

One row per provider, one column per vital - the cell is the share of sites passing, the small number the p75. No toggling needed to see where the category actually differs.

Google Optimize leads on LCP: 82% of its sites pass. LaunchDarkly trails at 49%. computed

The State of Web Vitals · Q2 2026 · 189,915 sites · all devices field datacorewebvitals.io/state-of-cwv
Related signals Maps (4) → CDN (16) → Chat & messaging (17) → Bot detection (8) → Chrome field data from 189,915 sites, representing millions of real page loads · How we measured
Live queries (3) — admin only
Query #1: 44.5 ms
SELECT 1 AS ok FROM site_providers LIMIT 0;
Query #2: 632.8 ms
SELECT CAST(p.key AS VARCHAR) AS e, COUNT(*) AS sites,
  COUNT(*) FILTER (WHERE s.crux."all".lcp IS NOT NULL) AS all_lcp_n,
  quantile_disc(s.crux."all".lcp, 0.5) AS all_lcp_median,
  quantile_disc(s.crux."all".lcp, 0.75) AS all_lcp_p75,
  COUNT(*) FILTER (WHERE s.crux."all".lcp <= 2500) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."all".lcp IS NOT NULL), 0) AS all_lcp_good,
  COUNT(*) FILTER (WHERE s.crux."all".lcp > 2500 AND s.crux."all".lcp <= 4000) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."all".lcp IS NOT NULL), 0) AS all_lcp_ni,
  COUNT(*) FILTER (WHERE s.crux."all".lcp > 4000) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."all".lcp IS NOT NULL), 0) AS all_lcp_poor,
  COUNT(*) FILTER (WHERE s.crux."all".inp IS NOT NULL) AS all_inp_n,
  quantile_disc(s.crux."all".inp, 0.5) AS all_inp_median,
  quantile_disc(s.crux."all".inp, 0.75) AS all_inp_p75,
  COUNT(*) FILTER (WHERE s.crux."all".inp <= 200) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."all".inp IS NOT NULL), 0) AS all_inp_good,
  COUNT(*) FILTER (WHERE s.crux."all".inp > 200 AND s.crux."all".inp <= 500) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."all".inp IS NOT NULL), 0) AS all_inp_ni,
  COUNT(*) FILTER (WHERE s.crux."all".inp > 500) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."all".inp IS NOT NULL), 0) AS all_inp_poor,
  COUNT(*) FILTER (WHERE s.crux."all".cls IS NOT NULL) AS all_cls_n,
  quantile_disc(s.crux."all".cls, 0.5) AS all_cls_median,
  quantile_disc(s.crux."all".cls, 0.75) AS all_cls_p75,
  COUNT(*) FILTER (WHERE s.crux."all".cls <= 0.1) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."all".cls IS NOT NULL), 0) AS all_cls_good,
  COUNT(*) FILTER (WHERE s.crux."all".cls > 0.1 AND s.crux."all".cls <= 0.25) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."all".cls IS NOT NULL), 0) AS all_cls_ni,
  COUNT(*) FILTER (WHERE s.crux."all".cls > 0.25) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."all".cls IS NOT NULL), 0) AS all_cls_poor,
  COUNT(*) FILTER (WHERE s.crux."all".fcp IS NOT NULL) AS all_fcp_n,
  quantile_disc(s.crux."all".fcp, 0.5) AS all_fcp_median,
  quantile_disc(s.crux."all".fcp, 0.75) AS all_fcp_p75,
  COUNT(*) FILTER (WHERE s.crux."all".fcp <= 1800) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."all".fcp IS NOT NULL), 0) AS all_fcp_good,
  COUNT(*) FILTER (WHERE s.crux."all".fcp > 1800 AND s.crux."all".fcp <= 3000) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."all".fcp IS NOT NULL), 0) AS all_fcp_ni,
  COUNT(*) FILTER (WHERE s.crux."all".fcp > 3000) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."all".fcp IS NOT NULL), 0) AS all_fcp_poor,
  COUNT(*) FILTER (WHERE s.crux."all".ttfb IS NOT NULL) AS all_ttfb_n,
  quantile_disc(s.crux."all".ttfb, 0.5) AS all_ttfb_median,
  quantile_disc(s.crux."all".ttfb, 0.75) AS all_ttfb_p75,
  COUNT(*) FILTER (WHERE s.crux."all".ttfb <= 800) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."all".ttfb IS NOT NULL), 0) AS all_ttfb_good,
  COUNT(*) FILTER (WHERE s.crux."all".ttfb > 800 AND s.crux."all".ttfb <= 1800) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."all".ttfb IS NOT NULL), 0) AS all_ttfb_ni,
  COUNT(*) FILTER (WHERE s.crux."all".ttfb > 1800) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."all".ttfb IS NOT NULL), 0) AS all_ttfb_poor,
  COUNT(*) FILTER (WHERE s.crux."phone".lcp IS NOT NULL) AS phone_lcp_n,
  quantile_disc(s.crux."phone".lcp, 0.5) AS phone_lcp_median,
  quantile_disc(s.crux."phone".lcp, 0.75) AS phone_lcp_p75,
  COUNT(*) FILTER (WHERE s.crux."phone".lcp <= 2500) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."phone".lcp IS NOT NULL), 0) AS phone_lcp_good,
  COUNT(*) FILTER (WHERE s.crux."phone".lcp > 2500 AND s.crux."phone".lcp <= 4000) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."phone".lcp IS NOT NULL), 0) AS phone_lcp_ni,
  COUNT(*) FILTER (WHERE s.crux."phone".lcp > 4000) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."phone".lcp IS NOT NULL), 0) AS phone_lcp_poor,
  COUNT(*) FILTER (WHERE s.crux."phone".inp IS NOT NULL) AS phone_inp_n,
  quantile_disc(s.crux."phone".inp, 0.5) AS phone_inp_median,
  quantile_disc(s.crux."phone".inp, 0.75) AS phone_inp_p75,
  COUNT(*) FILTER (WHERE s.crux."phone".inp <= 200) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."phone".inp IS NOT NULL), 0) AS phone_inp_good,
  COUNT(*) FILTER (WHERE s.crux."phone".inp > 200 AND s.crux."phone".inp <= 500) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."phone".inp IS NOT NULL), 0) AS phone_inp_ni,
  COUNT(*) FILTER (WHERE s.crux."phone".inp > 500) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."phone".inp IS NOT NULL), 0) AS phone_inp_poor,
  COUNT(*) FILTER (WHERE s.crux."phone".cls IS NOT NULL) AS phone_cls_n,
  quantile_disc(s.crux."phone".cls, 0.5) AS phone_cls_median,
  quantile_disc(s.crux."phone".cls, 0.75) AS phone_cls_p75,
  COUNT(*) FILTER (WHERE s.crux."phone".cls <= 0.1) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."phone".cls IS NOT NULL), 0) AS phone_cls_good,
  COUNT(*) FILTER (WHERE s.crux."phone".cls > 0.1 AND s.crux."phone".cls <= 0.25) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."phone".cls IS NOT NULL), 0) AS phone_cls_ni,
  COUNT(*) FILTER (WHERE s.crux."phone".cls > 0.25) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."phone".cls IS NOT NULL), 0) AS phone_cls_poor,
  COUNT(*) FILTER (WHERE s.crux."phone".fcp IS NOT NULL) AS phone_fcp_n,
  quantile_disc(s.crux."phone".fcp, 0.5) AS phone_fcp_median,
  quantile_disc(s.crux."phone".fcp, 0.75) AS phone_fcp_p75,
  COUNT(*) FILTER (WHERE s.crux."phone".fcp <= 1800) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."phone".fcp IS NOT NULL), 0) AS phone_fcp_good,
  COUNT(*) FILTER (WHERE s.crux."phone".fcp > 1800 AND s.crux."phone".fcp <= 3000) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."phone".fcp IS NOT NULL), 0) AS phone_fcp_ni,
  COUNT(*) FILTER (WHERE s.crux."phone".fcp > 3000) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."phone".fcp IS NOT NULL), 0) AS phone_fcp_poor,
  COUNT(*) FILTER (WHERE s.crux."phone".ttfb IS NOT NULL) AS phone_ttfb_n,
  quantile_disc(s.crux."phone".ttfb, 0.5) AS phone_ttfb_median,
  quantile_disc(s.crux."phone".ttfb, 0.75) AS phone_ttfb_p75,
  COUNT(*) FILTER (WHERE s.crux."phone".ttfb <= 800) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."phone".ttfb IS NOT NULL), 0) AS phone_ttfb_good,
  COUNT(*) FILTER (WHERE s.crux."phone".ttfb > 800 AND s.crux."phone".ttfb <= 1800) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."phone".ttfb IS NOT NULL), 0) AS phone_ttfb_ni,
  COUNT(*) FILTER (WHERE s.crux."phone".ttfb > 1800) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."phone".ttfb IS NOT NULL), 0) AS phone_ttfb_poor,
  COUNT(*) FILTER (WHERE s.crux."desktop".lcp IS NOT NULL) AS desktop_lcp_n,
  quantile_disc(s.crux."desktop".lcp, 0.5) AS desktop_lcp_median,
  quantile_disc(s.crux."desktop".lcp, 0.75) AS desktop_lcp_p75,
  COUNT(*) FILTER (WHERE s.crux."desktop".lcp <= 2500) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."desktop".lcp IS NOT NULL), 0) AS desktop_lcp_good,
  COUNT(*) FILTER (WHERE s.crux."desktop".lcp > 2500 AND s.crux."desktop".lcp <= 4000) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."desktop".lcp IS NOT NULL), 0) AS desktop_lcp_ni,
  COUNT(*) FILTER (WHERE s.crux."desktop".lcp > 4000) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."desktop".lcp IS NOT NULL), 0) AS desktop_lcp_poor,
  COUNT(*) FILTER (WHERE s.crux."desktop".inp IS NOT NULL) AS desktop_inp_n,
  quantile_disc(s.crux."desktop".inp, 0.5) AS desktop_inp_median,
  quantile_disc(s.crux."desktop".inp, 0.75) AS desktop_inp_p75,
  COUNT(*) FILTER (WHERE s.crux."desktop".inp <= 200) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."desktop".inp IS NOT NULL), 0) AS desktop_inp_good,
  COUNT(*) FILTER (WHERE s.crux."desktop".inp > 200 AND s.crux."desktop".inp <= 500) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."desktop".inp IS NOT NULL), 0) AS desktop_inp_ni,
  COUNT(*) FILTER (WHERE s.crux."desktop".inp > 500) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."desktop".inp IS NOT NULL), 0) AS desktop_inp_poor,
  COUNT(*) FILTER (WHERE s.crux."desktop".cls IS NOT NULL) AS desktop_cls_n,
  quantile_disc(s.crux."desktop".cls, 0.5) AS desktop_cls_median,
  quantile_disc(s.crux."desktop".cls, 0.75) AS desktop_cls_p75,
  COUNT(*) FILTER (WHERE s.crux."desktop".cls <= 0.1) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."desktop".cls IS NOT NULL), 0) AS desktop_cls_good,
  COUNT(*) FILTER (WHERE s.crux."desktop".cls > 0.1 AND s.crux."desktop".cls <= 0.25) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."desktop".cls IS NOT NULL), 0) AS desktop_cls_ni,
  COUNT(*) FILTER (WHERE s.crux."desktop".cls > 0.25) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."desktop".cls IS NOT NULL), 0) AS desktop_cls_poor,
  COUNT(*) FILTER (WHERE s.crux."desktop".fcp IS NOT NULL) AS desktop_fcp_n,
  quantile_disc(s.crux."desktop".fcp, 0.5) AS desktop_fcp_median,
  quantile_disc(s.crux."desktop".fcp, 0.75) AS desktop_fcp_p75,
  COUNT(*) FILTER (WHERE s.crux."desktop".fcp <= 1800) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."desktop".fcp IS NOT NULL), 0) AS desktop_fcp_good,
  COUNT(*) FILTER (WHERE s.crux."desktop".fcp > 1800 AND s.crux."desktop".fcp <= 3000) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."desktop".fcp IS NOT NULL), 0) AS desktop_fcp_ni,
  COUNT(*) FILTER (WHERE s.crux."desktop".fcp > 3000) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."desktop".fcp IS NOT NULL), 0) AS desktop_fcp_poor,
  COUNT(*) FILTER (WHERE s.crux."desktop".ttfb IS NOT NULL) AS desktop_ttfb_n,
  quantile_disc(s.crux."desktop".ttfb, 0.5) AS desktop_ttfb_median,
  quantile_disc(s.crux."desktop".ttfb, 0.75) AS desktop_ttfb_p75,
  COUNT(*) FILTER (WHERE s.crux."desktop".ttfb <= 800) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."desktop".ttfb IS NOT NULL), 0) AS desktop_ttfb_good,
  COUNT(*) FILTER (WHERE s.crux."desktop".ttfb > 800 AND s.crux."desktop".ttfb <= 1800) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."desktop".ttfb IS NOT NULL), 0) AS desktop_ttfb_ni,
  COUNT(*) FILTER (WHERE s.crux."desktop".ttfb > 1800) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE s.crux."desktop".ttfb IS NOT NULL), 0) AS desktop_ttfb_poor
                    FROM site_providers p
                    JOIN sites s ON s.origin = p.origin
                    WHERE p.category = 'ab_testing'
                      AND p.key IS NOT NULL AND CAST(p.key AS VARCHAR) <> ''
                    GROUP BY p.key;
Query #3: 35.5 ms
SELECT COUNT(*) AS n FROM sites;
JSON file lookups (5)
20260625/meta.json 0.05 ms
20260625/types.json 0.50 ms
20260625/loaf-scripts.json 0.45 ms
20260625/menu.json 0.15 ms
20260625/providers.json 5.01 ms