Type something to search...
How to Audit Web Font Performance with Lighthouse?

How to Audit Web Font Performance with Lighthouse?

A client sends you a screenshot of their PageSpeed Insights report: performance score 61, LCP 3.9 seconds, and a vague list of warnings. They want to know whether the six font files their theme loads are part of the problem. You could guess, but guessing is how teams spend a week shaving 20 KB off a font that was never on the critical path. Lighthouse, used carefully, tells you exactly how fonts are being discovered, downloaded, rendered, and swapped, and whether each one is costing you milliseconds or layout shift.

This article shows you how to run a Lighthouse audit focused on fonts, which audits and insights to read, how to confirm findings in DevTools, how to compare a page with and without its fonts, and how to keep font performance from regressing with Lighthouse CI.

What Lighthouse Can and Cannot Tell You About Fonts

Lighthouse is Google's open-source auditing tool. It loads a page in a controlled Chrome instance, applies simulated network and CPU throttling, and produces lab metrics plus a list of opportunities and diagnostics. You can run it from Chrome DevTools, PageSpeed Insights, the command line, or CI.

For fonts, Lighthouse is good at:

  • Spotting fonts that hide text while loading (missing or blocking font-display).
  • Showing where fonts sit in the request chain and whether they block rendering.
  • Identifying layout shifts caused by web font swaps.
  • Reporting how many bytes fonts add to the page.
  • Flagging missing or unused preconnects to font origins.

Lighthouse has limits you need to keep in mind:

  • It is a single lab run. One device profile, one network profile, one cold cache. Real users on real networks may see a very different font swap timing.
  • Simulated throttling estimates timings. By default Lighthouse loads the page quickly and then models what would happen on a slow connection. That model sometimes underestimates font-related shifts, which happen only when the font arrives after first paint.
  • It does not judge design. It cannot tell you whether you need five weights.

Treat Lighthouse as the place to find and explain font problems, and field data (CrUX in PageSpeed Insights, Search Console, or your own RUM) as the place to confirm they matter. For background on how fonts touch each metric, read how web fonts affect Core Web Vitals and layout shift.

Running a Clean Audit

Noise ruins font audits. Extensions inject fonts and scripts, and a warm cache hides download costs.

In Chrome DevTools

  1. Open the page in an Incognito window with extensions disabled.
  2. Open DevTools and go to the Lighthouse panel.
  3. Select Performance only, choose Mobile, and leave Simulated throttling on for the first run.
  4. Click Analyze page load.
  5. Run it three times and look at the median; individual runs vary.

From the command line

The CLI gives you repeatable runs and JSON output you can query:

npm install -g lighthouse

lighthouse https://example.com/blog/sample-post \
  --only-categories=performance \
  --output=html --output=json \
  --output-path=./reports/fonts \
  --chrome-flags="--headless=new"

This writes fonts.report.html and fonts.report.json. For a desktop profile, add --preset=desktop. To use real throttling instead of simulation, which catches late font swaps more reliably, add --throttling-method=devtools.

The Font-Related Findings to Read

Lighthouse 13 reorganized many classic audits into performance insights, the same analysis shown in the DevTools Performance panel. The names below are what you see in current reports; older reports show the legacy audit names in parentheses.

Font display

This insight (formerly "Ensure text remains visible during webfont load") lists every font file whose @font-face rule lets text stay invisible during loading, with an estimated time saving for each. If it appears, at least one face has no font-display value or uses block.

The fix is a single descriptor:

@font-face {
  font-family: "Brand Sans";
  src: url("/fonts/brand-sans.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
}

For Google Fonts loaded from the CDN, append &display=swap to the stylesheet URL. Choosing between swap, fallback, and optional is covered in what font-display is and which value to use.

Render-blocking requests

Font stylesheets from a third party, such as fonts.googleapis.com/css2, are render-blocking CSS. This insight (formerly "Eliminate render-blocking resources") shows how long they delay first paint. Self-hosting removes the extra origin; inlining the small @font-face block into your main CSS removes the extra request entirely.

Network dependency tree

This insight (formerly "Avoid chaining critical requests" and "Preconnect to required origins") draws the chain from HTML to CSS to font file. A typical problem chain looks like:

/blog/sample-post                    (HTML)
└─ fonts.googleapis.com/css2?...      (CSS, 120 ms)
   └─ fonts.gstatic.com/s/inter/...   (font, 340 ms)

Every level is a round trip that happens before the font can even start downloading. The insight also flags preconnect candidates and warns when you have unused preconnects or more than four, which waste connection setup. Fixes include self-hosting, adding a preload for the critical font, or adding a preconnect with crossorigin to the font origin if you must stay on a CDN. See how to preload web fonts the right way for the correct syntax.

Layout shift culprits

This insight (formerly "Avoid large layout shifts") lists the biggest layout shift clusters and their likely root causes. When a font swap is responsible, Lighthouse names the web font as the culprit for that shift. This is the most direct evidence you will get that a font is hurting CLS.

If you see it, the fix is a metric-matched fallback using size-adjust and the override descriptors, explained in how to use size-adjust and font metric overrides to prevent CLS.

LCP breakdown

When the LCP element is text, the LCP breakdown insight shows its render delay: the time between the HTML arriving and the text actually painting. A large render delay on a text LCP element, combined with a font display finding, points to text hidden while the font loads.

Network payload diagnostics

"Avoid enormous network payloads" lists the largest transfers. Fonts over 100 KB each are worth a closer look; a subset Latin WOFF2 is usually 20–50 KB per file. If you see TTF or WOFF files listed, you are not serving WOFF2. Subsetting to the characters you actually serve is the usual way to cut those bytes.

Querying the JSON Report

The HTML report is fine for a single page. For several pages, or for tracking over time, query the JSON with jq.

List every font request with its transfer size:

jq -r '.audits["network-requests"].details.items[]
  | select(.resourceType == "Font")
  | "\(.transferSize)\t\(.url)"' reports/fonts.report.json

Find every audit whose ID mentions fonts, along with its score:

jq -r '.audits | to_entries[]
  | select(.key | test("font"))
  | "\(.key)\t\(.value.score)\t\(.value.displayValue // "")"' reports/fonts.report.json

Pull the headline metrics:

jq '{
  lcp: .audits["largest-contentful-paint"].numericValue,
  cls: .audits["cumulative-layout-shift"].numericValue,
  fcp: .audits["first-contentful-paint"].numericValue
}' reports/fonts.report.json

Sum the bytes spent on fonts:

jq '[.audits["network-requests"].details.items[]
  | select(.resourceType == "Font") | .transferSize] | add' reports/fonts.report.json

Isolating Font Cost with an A/B Run

The cleanest way to quantify what fonts cost is to run Lighthouse twice: once normally, and once with font files blocked so the page falls back to system fonts.

# Baseline
lighthouse https://example.com/ --only-categories=performance \
  --output=json --output-path=./reports/with-fonts.json \
  --throttling-method=devtools --chrome-flags="--headless=new"

# Fonts blocked
lighthouse https://example.com/ --only-categories=performance \
  --output=json --output-path=./reports/no-fonts.json \
  --throttling-method=devtools --chrome-flags="--headless=new" \
  --blocked-url-patterns="*.woff2" --blocked-url-patterns="*.woff" \
  --blocked-url-patterns="*fonts.googleapis.com*"

Compare the two:

for f in with-fonts no-fonts; do
  jq -r --arg f "$f" '"\($f)\tLCP \(.audits["largest-contentful-paint"].numericValue | floor) ms\tCLS \(.audits["cumulative-layout-shift"].numericValue)"' \
    "reports/$f.json"
done

If LCP drops by 600 ms and CLS falls from 0.12 to 0.01 when fonts are blocked, you know exactly what the font setup is costing. If the numbers barely move, fonts are not your bottleneck, and you should look at images, JavaScript, or server response time instead. Run each variant at least three times and compare medians.

Confirming Findings in the Performance Panel

Lighthouse tells you what; the DevTools Performance panel shows you when.

  1. Open the Network panel, set throttling to Slow 4G, and tick Disable cache.
  2. In the Performance panel, set CPU throttling to 4× slowdown.
  3. Click the reload-and-record button.
  4. In the timeline, find the font requests in the Network track and the shifts in the Layout shifts track.

When a layout shift lines up with the moment a font file finishes, you have found a swap-induced shift. Click the shift to see which elements moved. The Insights sidebar in the Performance panel shows the same font display, layout shift culprit, and network dependency findings as Lighthouse, tied to this specific trace.

Two more DevTools checks are worth doing:

  • Network panel filtered to Font. Check that every file is WOFF2, that each is requested once (a duplicate usually means a preload missing crossorigin), and that the Initiator column shows the preload or your CSS rather than a third-party script.
  • Rendered Fonts. Select an element, open Computed, and scroll to Rendered Fonts to confirm the browser actually used your web font and not a synthesized bold or a fallback.

Preventing Regressions with Lighthouse CI

A single audit fixes today's problem. Lighthouse CI keeps a new theme, plugin, or designer request from reintroducing it.

npm install -D @lhci/cli
{
  "ci": {
    "collect": {
      "url": ["http://localhost:3000/", "http://localhost:3000/blog/sample-post"],
      "startServerCommand": "npm run preview",
      "numberOfRuns": 3,
      "settings": {
        "onlyCategories": ["performance"]
      }
    },
    "assert": {
      "assertions": {
        "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
        "largest-contentful-paint": ["warn", { "maxNumericValue": 2500 }],
        "resource-summary:font:count": ["warn", { "maxNumericValue": 3 }],
        "resource-summary:font:size": ["error", { "maxNumericValue": 120000 }]
      }
    },
    "upload": {
      "target": "temporary-public-storage"
    }
  }
}

Save that as lighthouserc.json and run:

npx lhci autorun

The build now warns when a page loads more than three font files, fails when fonts exceed about 120 KB, and fails when CLS goes over 0.1. Adjust the numbers to your own baseline; the point is that font weight becomes a tracked budget instead of a surprise.

A Font Audit Checklist

Work through these after each Lighthouse run:

  1. Does the font display insight list any files? Add font-display.
  2. Are font stylesheets render-blocking from a third-party origin? Self-host or inline the @font-face rules.
  3. How deep is the chain to the first font file? Preload the critical one.
  4. Does the layout shift culprits insight name a web font? Add a metric-matched fallback.
  5. Is any font file over 100 KB, or not WOFF2? Subset and convert.
  6. How many font files load on first view? Aim for one to three.
  7. Does blocking fonts change LCP or CLS significantly? If not, move on to other bottlenecks.
  8. Do field metrics agree with the lab? Check CrUX before and after your changes.

Lighthouse Font Audit FAQ

Lighthouse runs once from a fast machine and may receive the font before first paint, so no swap appears. Real users on slower networks see the fallback first and then a late swap. Try a run with DevTools throttling instead of simulated throttling and compare against CrUX field data.

Recent Lighthouse versions moved it into the Font display performance insight. It checks the same thing: whether any font-face rule lets text remain invisible while the font downloads, and how much time you could save by changing it.

Not directly. It reports file sizes and total payload, so an unexpectedly large font is your signal. You have to inspect the file itself, for example with fonttools, to see which characters it contains.

At least three, and five is better when you are comparing small changes. Use the median value. Font timing is sensitive to network variation, so a single run can easily mislead you.

The lab section of PageSpeed Insights is a Lighthouse run on Google's servers. PageSpeed Insights also shows CrUX field data for the URL and origin, which Lighthouse in DevTools does not, so it is the better starting point for deciding whether fonts are hurting real users.

Yes. Use resource-summary assertions to cap the number and total size of font files, and metric assertions for CLS and LCP. If a change adds a heavy font or causes a font-driven layout shift, the assertion fails and blocks the merge.

Conclusion

Lighthouse is most useful for fonts when you read the right findings: font display for invisible text, render-blocking requests and the network dependency tree for late discovery, layout shift culprits for swap-induced CLS, and payload diagnostics for oversized files. Run clean audits several times, query the JSON when you need precise numbers, and use a blocked-fonts comparison to measure what your typography actually costs.

Then close the loop. Confirm timing in the Performance panel, check that real-user data agrees, and add Lighthouse CI assertions so font count, font bytes, and CLS stay within budget as the site evolves. That turns font performance from a one-off cleanup into something your pipeline protects.

Here are some useful references for going deeper on auditing font performance:

  1. Chrome for Developers: Lighthouse overview — how to run Lighthouse from DevTools, the CLI, and Node.
  2. GitHub: Lighthouse CI — configuration, assertions, and the autorun command.
  3. Chrome for Developers: Analyze runtime performance — using the Performance panel to record and inspect page loads.
  4. web.dev: Best practices for fonts — the font loading techniques Lighthouse findings point toward.
  5. web.dev: Optimize Cumulative Layout Shift — diagnosing and fixing layout shifts, including those caused by web fonts.
Share :

Related Posts

Ascenders, Descenders, and Baselines: The Anatomy of a Letterform

Ascenders, Descenders, and Baselines: The Anatomy of a Letterform

You align an icon next to a button label and it looks a pixel or two too high, no matter how you adjust vertical-align. You set overflow: hidden

Continue Reading
Are Google Fonts GDPR-Compliant?

Are Google Fonts GDPR-Compliant?

In late 2022, thousands of small business owners in Germany and Austria opened letters demanding a few hundred euros in "damages" because their websi

Continue Reading
What Is the Best Font Size for Body Text on Websites?

What Is the Best Font Size for Body Text on Websites?

A client reviews their new site on a 14-inch laptop and says it looks great. Their sales director opens it on a phone in a sunny car park and can't r

Continue Reading