Google Fonts render-blocking: how to fix it
A Google Fonts link tag is a render-blocking stylesheet on another domain: the browser opens a connection to fonts.googleapis.com, downloads the CSS, then opens a second connection to fonts.gstatic.com for the files, and paints nothing until the CSS arrives. Self-hosting the woff2 files with font-display: swap and a preload removes all of it.
By Jose Pollman, who built this checker and writes the guides. Published How the tool ranks fixes
What a Google Fonts tag actually costs
The familiar snippet looks like one line:
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">The browser does this with it: open a connection to fonts.googleapis.com (DNS, TCP, TLS), download a stylesheet, discover inside it that the font files live on a second domain, fonts.gstatic.com, open a connection there, download the files. The stylesheet is render-blocking: nothing paints until it has arrived, and it cannot arrive until the first connection is up. On a phone over a mobile network that is commonly 300–800 ms before the first pixel, plus the font download itself. The render-blocking guide explains why any stylesheet in the head has this effect; this one is worse because it is on another origin.
Run the check on a page that uses Google Fonts: the render-blocking opportunity lists the css2 stylesheet, and the LCP timeline shows the render phase it lengthens.
Why preconnect is only a half-measure
The snippet Google now hands out adds two hints above the stylesheet:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>They start the two handshakes early, in parallel with the HTML parse, which trims perhaps 100–300 ms on a slow network. They do not remove the connections, they do not make the stylesheet non-blocking, and they do nothing about the second round trip for the font files. If you cannot self-host today, add them; they are strictly better than nothing. But treat them as the interim step, not the fix. The same goes for &display=swap in the URL: it stops text being invisible while the font loads, which matters, and it leaves the blocking stylesheet exactly where it was.
The fix: self-host
Serve the font files from your own domain, declare them in your own CSS with font-display: swap, and preload the one the first paint needs. No extra connections, no external stylesheet, and the font arrives with the page.
1. Get the files
The Google Fonts download button gives you TTF files, which are the wrong format. Use one of these instead:
- google-webfonts-helper (gwfh.mranftl.com): pick the family, weights and the latin subset, download woff2 files, copy the generated
@font-faceCSS. - Fontsource (
npm install @fontsource-variable/inter) for projects with a build step; it ships woff2 and the CSS. - Or fetch the CSS Google serves to a modern browser and download the woff2 URLs it references:
curl -s -A "Mozilla/5.0 (X11; Linux x86_64) Chrome/126" \
"https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" \
| grep -o "https://fonts.gstatic.com[^)]*woff2"Prefer a variable font where one exists: one file covers every weight, and a latin subset is usually under 40 KB.
2. Declare it
/* your stylesheet, or a <style> block in the head */
@font-face {
font-family: "Inter";
src: url("/fonts/inter-latin-var.woff2") format("woff2");
font-weight: 100 900; /* variable: the whole range in one file */
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}3. Preload the one that paints first
<link rel="preload" href="/fonts/inter-latin-var.woff2" as="font" type="font/woff2" crossorigin>crossorigin is required even on your own domain, because fonts are always fetched in anonymous mode; without it the browser downloads the file twice. Preload one or two files at most — the body face and perhaps the heading face — never the whole set.
4. Remove the Google tag
And the preconnect hints for the two Google domains that usually sit next to it. Leaving them costs two connections for nothing.
5. Serve the files well
Fingerprint the filename or set a long cache lifetime: Cache-Control: public, max-age=31536000, immutable. Fonts are the ideal candidate for the efficient cache policy audit.
Stopping the swap from shifting the page
With font-display: swap, text appears immediately in a fallback and then re-renders in the real font, and if the two have different metrics every line moves. Declare a fallback face with metrics tuned to the real font:
@font-face {
font-family: "Inter Fallback";
src: local("Arial");
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
body { font-family: "Inter", "Inter Fallback", system-ui, sans-serif; }The values differ per family; Chrome DevTools’ font panel and tools like the Fontaine or Capsize calculators produce them. The goal is that toggling the real font on and off in DevTools moves nothing. The CLS guide has the general version.
On WordPress
Themes and builders inject the Google tag themselves, so the fix is a setting rather than a template edit:
- Perfmatters: Fonts → Local Google Fonts, with Display Swap and Preload options. Also Disable Google Fonts if you switch to a system stack.
- OMGF (free): scans the pages, downloads the fonts, rewrites the tags.
- Astra, GeneratePress, Kadence, Blocksy: a “load Google Fonts locally” switch in the customizer’s typography or performance panel.
- Elementor: Settings → Advanced → Google Fonts Load: swap; Elementor Pro can host locally. Or choose a “custom font” you uploaded and set the theme to use it.
- Divi: Theme Options → General → disable Google Fonts, then upload the font under Divi’s custom fonts.
- Everything else: dequeue the handle (often
theme-name-fontsorgoogle-fonts) as in the render-blocking plugins guide, and add the@font-faceCSS above to the child theme.
On Shopify
Do not embed Google Fonts in theme.liquid. Shopify’s font library includes most of the popular families, served from Shopify’s own CDN on the same connection as your assets, with font-display: swap applied by the font_face filter. Choose the font in the theme editor’s typography settings and preload it:
{{ settings.type_body_font | font_url | preload_tag: as: 'font', type: 'font/woff2', crossorigin: 'anonymous' }}If the brand font is not in the library, upload the woff2 to Files, reference it in base.css with the @font-face block above, and remove any Google tag a previous developer left in the layout.
Or use no web font at all
The system font stack costs zero bytes and zero connections and looks native on every device. For body text it is often the right answer; keep a web font for headings if the brand needs it, which is what this site does.
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; }Check it worked
Re-run the check. The render-blocking opportunity should no longer list a googleapis stylesheet, the request count in the network panel should show no gstatic domain, and the render phase of the LCP timeline should be shorter. Toggle the font off in DevTools to confirm the fallback moves nothing; if it does, tune the override values.
Common questions
- Isn't the Google Fonts CSS cached across sites?
- Not any more. Since 2020 Chrome partitions its cache per site, and other browsers followed, so a font a visitor already has from another site is downloaded again for yours. The old argument for the shared CDN is gone; a self-hosted file is at least as fast and removes two connections.
- Is adding &display=swap to the Google Fonts URL enough?
- It fixes the invisible-text problem — text shows in a fallback until the font arrives — but the stylesheet itself still blocks rendering and still costs the two connections. Self-hosting fixes all three; the display parameter fixes one.
- Are Google Fonts a GDPR problem?
- A 2022 Munich court ruling held that loading fonts from Google's servers transmitted the visitor's IP address to Google without consent. Self-hosting removes the question entirely, which is one more reason it is the default recommendation in Europe.
- Which formats do I need?
- woff2 only. Every browser that matters has supported it since 2016; the woff, ttf and eot fallbacks in older snippets are dead weight. A latin subset of a variable font is typically 20–40 KB.
- Will self-hosting cause layout shift?
- The swap from fallback to real font can move text, self-hosted or not. font-display: swap plus a fallback face with size-adjust and ascent-override tuned to the real font makes the swap invisible. The CLS guides for WordPress and the general case cover the values.
Check a page against this
Enter any public URL. You’ll get the one change that recovers the most load time, what it’s worth, and the measurements underneath.
Related guides
- How to eliminate render-blocking CSS and JS — CSS and JS files stop anything appearing on screen.
- Render-blocking resources from WordPress plugins — Plugin CSS in the head, scripts without defer, and the settings that fix both.
- How to fix layout shift in WordPress — Unsized images, late fonts and injected bars, each with its setting.