At a glance the headline numbers for Fonts per page
How many web fonts load and their over-the-wire weight.
The typical page loads 4 web fonts weighing 154 KB.
Distribution & median INP site count and median INP at each level of fonts per page — n
Passing INP by fonts per page — n which level passes the INP most often
Fonts per page — n 4. p75 6. p99 20. At the low end (0–0): INP 106ms. At the high end (9+): INP 104ms. computed
Distribution & median INP site count and median INP at each level of fonts per page — size
Passing INP by fonts per page — size which level passes the INP most often
Fonts per page — size 154 KB. p75 295 KB. p99 1.4 MB. At the low end (0–49 KB): INP 96ms. At the high end (550+ KB): INP 104ms. computed
Why this matters for the Core Web Vitals, and where to start fixing it
Every font face is a separate file, and most sites load one per weight and style. Three families with three weights each is nine downloads of render-path bytes. Text either waits for them or reflows as they land, one by one.
The audit is quick: which weights actually appear above the fold? A variable font collapses a whole family of weights into one file. A system font stack costs nothing at all.
Chrome field data from 189,915 sites, representing millions of real page loads. How we measured.
Live queries (4) — admin only
SELECT COUNT(*) AS count,
quantile_disc(m.fonts.total.n, 0.10) AS p10,
quantile_disc(m.fonts.total.n, 0.25) AS p25,
quantile_disc(m.fonts.total.n, 0.50) AS p50,
quantile_disc(m.fonts.total.n, 0.75) AS p75,
quantile_disc(m.fonts.total.n, 0.90) AS p90,
quantile_disc(m.fonts.total.n, 0.99) AS p99
FROM sites WHERE m.fonts.total.n IS NOT NULL;
SELECT CASE WHEN m.fonts.total.n >= 0 AND m.fonts.total.n < 1 THEN 0 WHEN m.fonts.total.n >= 1 AND m.fonts.total.n < 2 THEN 1 WHEN m.fonts.total.n >= 2 AND m.fonts.total.n < 3 THEN 2 WHEN m.fonts.total.n >= 3 AND m.fonts.total.n < 4 THEN 3 WHEN m.fonts.total.n >= 4 AND m.fonts.total.n < 5 THEN 4 WHEN m.fonts.total.n >= 5 AND m.fonts.total.n < 6 THEN 5 WHEN m.fonts.total.n >= 6 AND m.fonts.total.n < 7 THEN 6 WHEN m.fonts.total.n >= 7 AND m.fonts.total.n < 8 THEN 7 WHEN m.fonts.total.n >= 8 AND m.fonts.total.n < 9 THEN 8 WHEN m.fonts.total.n >= 9 THEN 9 END AS bucket_idx, COUNT(*) AS n,
quantile_disc(crux."phone".inp, 0.5) AS median,
COUNT(*) FILTER (WHERE crux."phone".inp IS NOT NULL AND crux."phone".inp <= 200) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE crux."phone".inp IS NOT NULL), 0) AS good_pct
FROM sites WHERE m.fonts.total.n IS NOT NULL GROUP BY bucket_idx;
SELECT COUNT(*) AS count,
quantile_disc(m.fonts.total.size, 0.10) AS p10,
quantile_disc(m.fonts.total.size, 0.25) AS p25,
quantile_disc(m.fonts.total.size, 0.50) AS p50,
quantile_disc(m.fonts.total.size, 0.75) AS p75,
quantile_disc(m.fonts.total.size, 0.90) AS p90,
quantile_disc(m.fonts.total.size, 0.99) AS p99
FROM sites WHERE m.fonts.total.size IS NOT NULL;
SELECT CASE WHEN m.fonts.total.size >= 0 AND m.fonts.total.size < 50 THEN 0 WHEN m.fonts.total.size >= 50 AND m.fonts.total.size < 100 THEN 1 WHEN m.fonts.total.size >= 100 AND m.fonts.total.size < 150 THEN 2 WHEN m.fonts.total.size >= 150 AND m.fonts.total.size < 200 THEN 3 WHEN m.fonts.total.size >= 200 AND m.fonts.total.size < 250 THEN 4 WHEN m.fonts.total.size >= 250 AND m.fonts.total.size < 300 THEN 5 WHEN m.fonts.total.size >= 300 AND m.fonts.total.size < 350 THEN 6 WHEN m.fonts.total.size >= 350 AND m.fonts.total.size < 400 THEN 7 WHEN m.fonts.total.size >= 400 AND m.fonts.total.size < 450 THEN 8 WHEN m.fonts.total.size >= 450 AND m.fonts.total.size < 500 THEN 9 WHEN m.fonts.total.size >= 500 AND m.fonts.total.size < 550 THEN 10 WHEN m.fonts.total.size >= 550 THEN 11 END AS bucket_idx, COUNT(*) AS n,
quantile_disc(crux."phone".inp, 0.5) AS median,
COUNT(*) FILTER (WHERE crux."phone".inp IS NOT NULL AND crux."phone".inp <= 200) * 1.0 / NULLIF(COUNT(*) FILTER (WHERE crux."phone".inp IS NOT NULL), 0) AS good_pct
FROM sites WHERE m.fonts.total.size IS NOT NULL GROUP BY bucket_idx;