Web fonts without font-display
What is this issue?
When a page uses a web font, most browsers hide the text set in it until the font file
has downloaded -- for up to three seconds. The CSS font-display property tells the
browser to show the text in a fallback font in the meantime.
This suggestion is raised when Lighthouse finds at least one web font on the page that
does not declare a font-display strategy.
Why it matters
- Invisible text is a blank page to the visitor, even though the HTML has arrived.
- It can delay First Contentful Paint when the first content on the page is text in a web font.
- It is a small, visitor-facing fix with little ranking effect on its own, which is why this is a suggestion.
How to fix it
Add
font-displayto every@font-face:@font-face { font-family: "Brand"; src: url("/fonts/brand.woff2") format("woff2"); font-display: swap; }swapshows the fallback at once;optionalavoids the swap entirely on slow connections.For Google Fonts, add
&display=swapto the stylesheet URL.Preload the one or two fonts the first screen needs so the swap happens sooner.
Examples
1. Self-hosted font
Fails:
@font-face { font-family: Inter; src: url(/fonts/inter.woff2); }Passes:
@font-face { font-family: Inter; src: url(/fonts/inter.woff2); font-display: swap; }2. Google Fonts
Fails: https://fonts.googleapis.com/css2?family=Inter
Passes: https://fonts.googleapis.com/css2?family=Inter&display=swap
How PixyScan detects this
Chooses which pages to measure. After the crawl, PixyScan measures the homepage plus the pages your own site links to most, up to a per-scan budget (ten pages by default, at most fifty). Measuring every page would take hours and exhaust the PageSpeed Insights quota of the API key it runs under.
Asks the PageSpeed Insights API about each of them, on mobile. This needs the Hobby plan or above and a PageSpeed Insights API key on the site or its workspace.
Reads the Lighthouse audit
font-display(in newer Lighthouse versions,font-display-insight), which lists each web font whose text stayed invisible while it loaded.Raises the suggestion when Lighthouse lists at least one font. The audit is pass/fail in Lighthouse too.
Lighthouse renamed several audits in versions 12 and 13 (the "insight" audits shared with Chrome DevTools). PixyScan reads whichever one the response carries, and the finding names the audit it was read from.
Raises nothing on a page it did not measure. A page outside the sample, and a page whose measurement failed, are both reported as
Not measured. Neither is treated as passing, and neither can fail this check.
What we store
Storage Level
Page Level
Database Table / Prisma Model
PagePerformance (page_performance), with the finding itself on
audit_issues.details.
Fields Used
| Field | Type | Description |
|---|---|---|
| strategy | String | mobile or desktop. Every number in the row belongs to one form factor. |
| fetchedAt | DateTime | When PageSpeed answered. "Measured three weeks ago" is not "not measured". |
| errorReason | String? | Why a sampled page still has no numbers. Null when the call succeeded. |
| audits | Json? | A compact summary of the Lighthouse audits the opportunity checks read: per audit its source (the Lighthouse audit id it was read from), score, numericValue, displayValue, itemCount, totals over every row (wastedBytes, wastedMs, blockingTime, mainThreadTime) and the five worst rows. Keyed by the classic Lighthouse audit id. Null when the response carried no audits. |
On the finding (audit_issues.details): message, plus audit (font-display or font-display-insight), count (fonts listed), wastedMs and items (each font's url and wastedMs).
Detection Dependencies
- PageSpeed Insights API v5 (
lighthouseResult.audits), mobile strategy - The PageSpeed plan feature (Hobby and above) and a PageSpeed Insights API key
- The crawl's internal link graph, which decides which pages are sampled
Note
A page with no row was never measured. The pass samples the homepage plus the
most-linked pages within a per-scan budget (default 10, at most 50), so most pages of a
site have no page_performance row at all. That is the normal state, it is drawn as
Not measured, and this check cannot be raised against it.
A row whose metric columns are all null with an errorReason set is a different state
again: the page WAS sampled and the measurement failed. It is still never reported as
passing.