Preloading Fonts Made Our WordPress Site Slower: The 0.9s LCP Lesson
Everyone says preload your fonts. Nobody mentions the case where preloading makes Largest Contentful Paint worse by almost a full second. I measured it, then wrote a plugin rule to make sure it never happens again.
The setup
I run a small web design studio in Ottawa. Our theme, like a lot of hand-tuned WordPress themes, declares its fonts inline in the HTML head. A few months ago I was building a font-loading optimizer plugin: it finds the WOFF2 files your site actually uses, preloads them, adds font-display: swap, and removes the render-blocking font requests.
The first version preloaded everything it found. Inline declarations, external stylesheet declarations, all of it. That looked correct. Every performance guide says preloads go in the head, fonts are critical, done.
The measurement
I ran the A/B on PageSpeed Insights mobile, three runs per state, same page, nothing else changed.
The version with preloads on an inline-declaring theme came back with LCP roughly 0.9 seconds worse.
Not better. Worse. By almost a second.
Why it happens
When a font is declared inline in the HTML head, the browser discovers it during the initial parse. It is already as early as it can possibly be. A preload hint for that same font does not make the browser fetch it earlier. What the preload does is add a second, parallel discovery path for a file the browser was already handling, plus the bandwidth priority competition that comes with it.
On our theme, that competition pushed the actual LCP resource down the priority queue. The hero text waited on bandwidth that the duplicate preload was eating.
The rule that fell out of the measurement:
Preload fonts that are declared in external stylesheets. Never preload fonts declared inline in the HTML head.
For external declarations the story is the opposite. The browser cannot know about those fonts until it has downloaded and parsed the whole CSS file. A preload hint there starts the font fetch at time zero, in parallel with the CSS download, instead of after the parse. That is where preloading earns its keep. I verified the timing in the network waterfall: with the plugin, font requests on external-CSS pages start alongside the CSS fetch instead of trailing it.
What shipped
The plugin now applies that rule before it emits anything:
- Find WOFF2 files referenced from enqueued external stylesheets (self-hosted, up to a static cap of 4 per page).
- Skip anything declared inline in the head. Not "deprioritize". Skip.
- Emit preloads with
font-display: swaponly for the external set. - Leave a short audit trail so you can see what it chose and why.
The A/B suite after the rule change: zero LCP regression on inline-declaring themes (the plugin correctly emits no preloads there), and parallel fetch timing verified on external-CSS pages. Our own site sits at 100 mobile PageSpeed with LCP between 0.93 and 1.1 seconds across our four key pages, and I am not willing to trade that for a checklist item.
The cap at 4 matters too. I have seen plugins preload eight or nine font files because "more preloads equals more speed". On a normal site with two to four families, everything past the real set is pure bandwidth theft from the LCP resource.
The broader lesson
Performance advice is almost always written for the average case. Your theme is not the average case. The only honest way to know what a change does to your LCP is to measure it on your own pages, before and after, more than once. A single PageSpeed run has noise in it. Three runs per state is the minimum I trust for a claim like "this made things faster".
The specific trap here has a shape you can generalize: an optimization that duplicates work the browser already does is a cost, not an optimization. Preloading an already-inline font. Preloading the LCP image that sits first in your markup. Hinting at resources the parser would find in the next ten milliseconds anyway. All the same trap.
If you want to check whether your WordPress site has this problem, the quick test is to open your page source and search for @font-face in the head. If your fonts are declared there, preloading them is not buying you anything. If they live in a .css file, preloading the WOFF2 files is one of the cheapest LCP wins available.
The plugin is PeakSpire Font Loading Optimizer on wordpress.org. It collects nothing, phones home nowhere, and applies the inline-skip rule by default because that is the only version of it I could measure and stand behind.
Have you measured a preload backfire on your own stack? I would like to hear how common this actually is outside WordPress.
Top comments (1)
Saw the exact same thing outside WordPress on static Astro and Next.js builds. Preloading extra
.woff2weights or subsets that are not painted above the fold competes directly with the CSS and hero image on throttled 4G connections, or simply duplicates work if@font-faceis already inlined in<head>.Another easy trap to miss is forgetting
crossoriginon<link rel="preload" as="font">: even for self-hosted fonts on the same origin, leaving it off causes browsers to fetch the file twice (once uncredentialed for preload and again in CORS mode for CSS), burning early bandwidth right before LCP.