Google Fonts vs Self-Hosting: Making the Delivery Decision
This guide is part of the Font Loading & Delivery Strategies blueprint, and it answers one of the most consequential delivery questions a frontend team makes: do you let fonts.googleapis.com serve your typefaces, or do you copy the WOFF2 files onto your own origin and control every byte yourself?
The decision used to be easy. For years the standard advice was "use Google Fonts — millions of sites share the same cached font file, so your users probably already have it." That argument is now factually dead, and most of the remaining reasons to outsource font delivery have eroded with it. This guide walks through what actually changed, quantifies the cost of the third-party hop, and gives you a tested migration workflow with verification at each step. Start your diagnosis in Chrome DevTools: open the Network panel, filter by Font, reload, and watch how many cross-origin connections and serialized requests stand between the browser and the first painted glyph.
The Myth of the Shared Google Fonts Cache
The single most repeated justification for hosted Google Fonts — "the file is already cached from another site" — stopped being true in 2020. Browsers used to keep one global HTTP cache keyed only by URL, so gstatic.com's Roboto WOFF2 fetched on site A would be reused on site B for free. That created a privacy side channel: a site could time cache hits to fingerprint which other sites you had visited.
To close the leak, every major engine adopted HTTP cache partitioning, keying the cache by the top-level site as well as the resource URL. Chrome 86 (October 2020), Firefox 85 (January 2021), and Safari (which partitioned even earlier) all ship this. The practical consequence: a font on a shared CDN is fetched and stored separately for every site that uses it. There is no cross-site reuse left to harvest. We cover the mechanics and the measurement in depth in browser font caching mechanics and the partitioning specifics in HTTP cache partitioning and cross-site fonts.
Once you accept that the cache is per-site, the comparison collapses to a clean question: which delivery path is faster and safer for the first visit to your site? On that question, self-hosting wins on almost every axis.
Privacy, GDPR, and the Legal Surface
Self-hosting is no longer only a performance argument — it is increasingly a compliance one. When a browser requests a font from fonts.googleapis.com and fonts.gstatic.com, it transmits the visitor's IP address (personal data under the GDPR) to a third party in another jurisdiction, without the visitor's prior consent. A German court (Munich, January 2022) ruled that embedding Google Fonts dynamically and thereby passing a visitor's IP to Google violated the GDPR, and awarded damages. A wave of warning letters followed across the EU.
Self-hosting eliminates that transfer entirely: the font request is same-origin, the IP never leaves your infrastructure, and no consent banner or data-processing agreement is required for the font itself. Even outside the EU, removing an unnecessary third-party data flow is a defensible default. If your legal or privacy team has any view on third-party requests, hosting fonts yourself is the conservative choice.
The Cost of the Third-Party Hop
Beyond privacy, the hosted path is structurally slower on the visit that matters — the first one, where nothing is cached. Two distinct costs stack up.
Extra DNS and connection setup. Hosted Google Fonts spans two domains: the CSS lives on fonts.googleapis.com and the font binaries on fonts.gstatic.com. Each is a fresh hostname requiring a DNS lookup, TCP handshake, and TLS negotiation — roughly one to three round trips each before a single byte of font data is requested. On a high-latency mobile connection at 150ms RTT, those handshakes alone can add 300–600ms before the font even begins downloading. You can soften this with <link rel="preconnect"> to both domains, but that is mitigation for a cost self-hosting simply does not incur.
The two-request CSS-then-font waterfall. Hosted Google Fonts cannot be preloaded directly, because the URL of the actual WOFF2 file is not known until the browser parses the CSS at fonts.googleapis.com. So the sequence is strictly serial: fetch the CSS, parse it, discover the @font-face src, then open a connection to gstatic.com and fetch the font. That is two round-trip-bound requests in sequence on the critical path. Because the font URL is hidden inside the CSS, no rel="preload" hint can pull the font forward — you would be guessing at a hashed, version-pinned URL that Google rotates. This serialized dependency is exactly the kind of chain that delays your Largest Contentful Paint when the LCP element is text.
What Self-Hosting Buys You
Moving the files to your own origin unlocks the full toolbox of resource hints and cache control:
- Direct preload. You know the exact font URL, so you can emit
<link rel="preload" as="font" type="font/woff2" crossorigin>and pull the critical weight into the connection the browser already has open for your HTML. No discovery delay. Cache-Control: immutable. You set your own headers. Content-hash the filename and serveCache-Control: public, max-age=31536000, immutableso repeat visits never revalidate. Google's CSS, by contrast, is served with a short max-age that forces periodic revalidation.- Subsetting control. You decide which glyphs ship. Run
pyftsubset/glyphhangerto cut a 200KB+ family to a sub-50KB Latin subset, and split scripts with unicode-range subset loading. Google's default subsets are coarse and not tuned to your content. - Single-origin connection reuse. Over HTTP/2 the font shares the same multiplexed connection as your HTML, CSS, and JS — zero extra handshakes.
- Stability. Your fonts cannot break because a third party rotated a URL, changed a subset, or had an outage.
When Hosted Google Fonts Is Still Fine
Self-hosting is the right default, but hosted delivery is not a crime. It is a reasonable choice when:
- You are prototyping or building an internal tool where load performance and GDPR exposure are irrelevant.
- Body text is the only thing using the webfont and you have already set font-display to
swapso there is no blocking — the third-party latency only delays the swap, not first paint. - You have no build pipeline and no place to host static assets with long cache headers.
If none of those apply — and for any production marketing or product site they usually do not — self-host.
Decision Matrix
| Factor | Hosted Google Fonts | Self-Hosted |
|---|---|---|
| Cross-site cache reuse | None (cache partitioned since 2020) | None — but irrelevant, you control caching |
| Extra DNS/TLS setup | 2 domains (googleapis + gstatic) | 0 — same origin |
| Request waterfall | Serial: CSS then font | Single preloadable font fetch |
| Preload the actual font | Not possible (URL hidden in CSS) | Yes, rel="preload" |
| Cache-Control headers | Google-controlled, revalidates | Yours: immutable, 1-year |
| Subsetting control | Coarse default subsets | Full pyftsubset control |
| GDPR / IP transfer | IP sent to Google | No third-party transfer |
| Resilience to upstream changes | Depends on Google URLs | Fully self-contained |
| Setup effort | Paste one <link> |
Build step + headers |
Baseline Configuration
Start from a correct same-origin @font-face and preload. This is the minimum setup before any further tuning.
Baseline self-hosted @font-face with preload
<!-- In <head>, before the stylesheet that uses the font -->
<link rel="preload" href="/fonts/inter-latin-400.woff2" as="font" type="font/woff2" crossorigin>
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-latin-400.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
Note that crossorigin is required on the preload link even for same-origin fonts, because font fetches are always made in CORS mode; omit it and the browser fetches the file twice.
If your fonts sit on a separate static-asset hostname (your own CDN), add a preconnect to that hostname and serve the font with permissive CORS so the preload is honoured:
Same-origin or self-CDN font headers
# Long-lived, content-hashed font assets
location ~* \.woff2$ {
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Access-Control-Allow-Origin "*"; # if fonts live on a separate CDN host
types { font/woff2 woff2; } # correct MIME type
}
The immutable directive tells the browser never to revalidate the file within its max-age, so repeat visits read it straight from disk cache — a guarantee you cannot make about Google's CSS, which is served with a short max-age and revalidates periodically.
Step-by-Step Migration Workflow
Each step below ends with a verification check so you never ship a half-migrated font setup.
Implementation Steps:
- Inventory the current request. In DevTools Network, reload and record every request to
fonts.googleapis.comandfonts.gstatic.com. Note the families, weights, and styles actually used. Verify: you have a list of exactfont-family/weight/stylecombinations to reproduce. - Obtain the WOFF2 files. Pull the same families from an open-source source (the upstream font repo) or extract them, then run
pyftsubsetto a Latin subset:pyftsubset Inter.ttf --output-file=inter-latin-400.woff2 --flavor=woff2 --unicodes="U+0000-00FF,U+2000-206F" --layout-features=kern,liga. Verify: each output WOFF2 is under your budget (target < 50KB/subset). - Place files and content-hash them. Move WOFF2 files into
/fonts/(or your asset pipeline) with hashed filenames so you can cache them forever. Verify: the built filename contains a hash, e.g.inter-latin-400.a1b2c3.woff2. - Write same-origin
@font-facerules. Reproduce every weight/style from step 1 as a@font-faceblock pointing at the local file, each with an explicitfont-weight,font-style, andfont-display. Verify:document.fontsin the console lists each face;document.fonts.check('1rem Inter')returnstrueafter load. - Preload the above-the-fold weight. Add
<link rel="preload" as="font" type="font/woff2" crossorigin>for the single weight your LCP text uses — and only that one. Verify: DevTools shows the font fetched at Highest priority, early in the waterfall, with no "preloaded but not used" warning. - Set cache headers. Serve
/fonts/*withCache-Control: public, max-age=31536000, immutable. Verify: the response headers in Network showimmutable; a second navigation reads the font from disk cache (size column shows "(disk cache)"). - Remove the Google
<link>tags. Delete the<link>tofonts.googleapis.comand anypreconnecttogstatic.com. Verify: reload with the Network panel filtered to "Font" — there are zero cross-origin requests; every font is same-origin. - Re-measure. Run Lighthouse and compare LCP before/after. Verify: LCP drops (typically 100–400ms on text-LCP pages) and the "Reduce unused CSS"/"third-party" notes about Google Fonts are gone.
HTTP/2, CDN Edge, and Connection Economics
A frequent counter-argument is that Google's edge network is faster than your origin, so the third-party hop pays for itself. In practice this rarely holds for fonts, and here is the precise reasoning.
Over HTTP/2 (and HTTP/3), a browser multiplexes many requests over a single connection to one origin. When your fonts live on your own domain, they ride the connection the browser already opened for the HTML document — no new handshake, no new congestion-window warm-up. The hosted path, by contrast, forces the browser to open two brand-new connections (googleapis for CSS, gstatic for the binary), each starting with a cold congestion window and each paying full DNS + TCP + TLS setup. On a 4G link at ~100–150ms RTT, that is the dominant cost — it dwarfs any raw throughput advantage Google's edge might have for a sub-50KB file that fits in a handful of packets.
If you genuinely need edge proximity, put your own fonts on your own CDN behind your own hostname. You still get edge caching and you keep the single-origin connection-reuse benefit (assuming the CDN hostname is the same as, or preconnected alongside, your document origin). The decision is therefore not "Google's edge vs my slow origin" — it is "two cold third-party connections vs one warm same-origin connection," and the warm connection wins on the first paint that matters.
There is also a tail-latency argument. A third-party dependency adds a point of failure outside your control: if gstatic.com is slow or blocked (corporate proxies, regional filtering, ad-blockers that strip Google requests), your text rendering stalls behind it. Self-hosted fonts share the fate of your own origin — if your HTML loaded, your font will too.
Preconnect mitigation for sites that keep hosted Google Fonts
<!-- If you must stay on hosted Google Fonts, at least warm both connections
early so the DNS+TLS cost overlaps with HTML parsing instead of blocking. -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter&display=swap">
This is the best the hosted path can do — and note it is still two connections you would not need at all after migrating. The crossorigin on the gstatic preconnect is required because the font fetch is in CORS mode; the googleapis preconnect (CSS) is not crossorigin.
Browser Compatibility & Performance Matrix
| Capability | Chrome/Edge | Firefox | Safari |
|---|---|---|---|
| WOFF2 support | 36+ | 39+ | 10+ |
rel="preload" for fonts |
50+ | 85+ | 11.1+ |
fetchpriority="high" |
102+ | 119+ | 17.2+ |
| HTTP cache partitioning | 86+ | 85+ | Yes (early) |
font-display descriptor |
60+ | 58+ | 11.1+ |
Cache-Control immutable |
Honored | 49+ | Honored |
fetchpriority is a progressive enhancement; older browsers ignore the attribute and fall back to default preload priority, so it is always safe to include.
Measuring the Before and After
Migrations are only worth shipping if you can prove the win, so capture a baseline before you touch anything. Three measurements matter, all available without leaving the browser:
- Connection count to font domains. In the Network panel, count distinct cross-origin font requests. Hosted Google Fonts produces requests to two domains; the target after migration is zero cross-origin font requests.
- Font request start time. Hover the WOFF2 request in the waterfall and read its Start time relative to navigation. Self-hosting plus preload should move the font's start dramatically earlier because it no longer waits to be discovered through a third-party CSS file.
- LCP, in the lab and the field. Run Lighthouse for a lab number, but also watch your real-user monitoring — lab conditions understate the connection-setup cost that high-latency mobile users actually pay, which is exactly where self-hosting helps most.
Record these three before and after. A typical text-LCP marketing page shows the font request starting hundreds of milliseconds earlier and LCP dropping in proportion, with the two third-party connections gone entirely from the waterfall.
Common Pitfalls
- Preloading every weight. Preloading more than the one or two above-the-fold weights steals bandwidth from the LCP resource and can regress LCP. Preload only what renders first.
- Omitting
crossoriginon the preload. Without it the preloaded font is in a different CORS mode than the actual fetch, so the browser discards it and downloads the font a second time. - Forgetting
font-display. A bare@font-facedefaults tofont-display: auto, which most browsers treat likeblock— up to 3s of invisible text. Always setswap,fallback, oroptional. - Shipping the whole family unsubsetted. Copying Google's multi-script files onto your origin without subsetting gives you the third-party hop's download size without its convenience. Subset to the scripts you serve.
- Short cache lifetimes. Self-hosting without
immutablelong-lived headers wastes the main advantage — repeat-visit caching that you fully control. - Leaving a stray
preconnectto gstatic. After migration, a leftover<link rel="preconnect" href="https://fonts.gstatic.com">opens a connection you never use, wasting a handshake.
Frequently Asked Questions
Doesn't the shared Google Fonts cache make hosted fonts faster for return visitors across sites?
No. Since Chrome 86, Firefox 85, and Safari's earlier change, the HTTP cache is partitioned by top-level site. A font fetched on one site is stored under that site's partition and is never reused when the same font is requested by a different site. The cross-site "free cache hit" has not existed since 2020, so it cannot offset the extra DNS, connection, and waterfall costs of the third-party path.
Is self-hosting Google Fonts legal?
Yes, for the open-source families Google distributes (most are under the SIL Open Font License). The OFL explicitly permits redistribution and self-hosting as long as you do not sell the fonts by themselves. Self-hosting actually improves your legal position under the GDPR because the visitor's IP is no longer transmitted to a third party.
When is it acceptable to keep using hosted Google Fonts?
For prototypes, internal tools, or sites where the webfont is body text only with font-display: swap already set — in that case the third-party latency only delays the swap-in, not first paint, and the GDPR exposure may be acceptable for a non-public tool. For any production, public-facing site, self-host.
How much LCP improvement should I expect from migrating?
It depends on whether your LCP element is text. On text-LCP pages over high-latency mobile, eliminating two extra connection setups and the serial CSS-then-font waterfall, then preloading the critical weight, commonly recovers 100–400ms of LCP. On image-LCP pages the gain is smaller but the privacy and resilience benefits still apply.