
How to Self-Host Google Fonts for Better Performance and Privacy?
- Sajjad
- Typography
- 01 Oct, 2026
You run a WebPageTest on a client's site and the waterfall shows the same pattern on every page. The HTML arrives, the CSS arrives, and then the browser opens a connection to fonts.googleapis.com to fetch a stylesheet, which points to fonts.gstatic.com, which needs yet another DNS lookup, TCP connection, and TLS handshake before the first font byte arrives. On mobile that is easily 300–600ms of waiting before the headline can render in the right typeface. Meanwhile, the client's legal team has forwarded an email about a German court ruling and asks whether the site is sending visitor IP addresses to Google.
Self-hosting solves both problems. This article explains why self-hosting is now the better default, how to get the right files, how to write the @font-face rules, how to serve them with correct headers, and the shortcuts available in Next.js, npm-based builds, and WordPress.
What Self-Hosting Means
Self-hosting a Google Font means downloading the font files and serving them from your own domain or CDN instead of linking to Google's hosted API. The fonts are the same; only the delivery changes.
| Aspect | Google Fonts API | Self-hosted |
|---|---|---|
| Origins contacted | fonts.googleapis.com and fonts.gstatic.com | Your own origin only |
| Connection setup | Two extra connections on first visit | None beyond your existing connection |
| Cross-site caching | None (browsers partition caches by site since 2020) | Same as any of your assets |
| Visitor data to Google | IP address and user agent with every request | None |
| Control over files | Google decides formats, subsets, and updates | You control subsetting, versions, and cache headers |
| Maintenance | Automatic updates | You update files when you choose |
The old argument for the hosted API was the shared cache: a visitor who already had Roboto cached from another site would not download it again. That benefit disappeared when Chrome, Safari, and Firefox partitioned their HTTP caches by top-level site. Today a font from fonts.gstatic.com is downloaded fresh for every site that uses it.
Why It Is Faster
- No extra connections. Fonts load over the HTTP/2 or HTTP/3 connection your HTML already opened. That removes one or two rounds of DNS, TCP, and TLS.
- No render-blocking third-party CSS. The Google stylesheet is a render-blocking request to another origin. Self-hosted
@font-facerules live in your own CSS bundle. - Earlier discovery. You can preload the exact file URL, which is impossible with Google's hosted files because their URLs are versioned and can change.
- Full cache control. You can set a one-year immutable cache on versioned filenames.
Why It Is Better for Privacy
Every request to Google's font servers sends the visitor's IP address and user agent to Google. In January 2022, the Munich Regional Court (Landgericht München I) ruled that embedding Google Fonts this way, without consent, violated the GDPR, and awarded the plaintiff €100 in damages. The ruling triggered a wave of warning letters to German site owners.
Self-hosting removes the third-party transfer entirely, so there is nothing to ask consent for and nothing to list in your cookie banner for fonts. For the legal detail, see our separate post on whether Google Fonts are GDPR-compliant.
Is Self-Hosting Allowed?
Yes. Nearly every family on Google Fonts is released under the SIL Open Font License (OFL) or the Apache License 2.0, both of which permit you to download, embed, and redistribute the fonts on your website, including commercial sites. You do not need to credit Google. If you modify a font that has a Reserved Font Name under the OFL, check the license terms, because a renamed build may be required.
Step 1: Get the Right Files
You want WOFF2 files, split by script subset, and ideally the variable version if the family has one. A variable Inter in WOFF2 covering Latin is around 50–70KB and replaces every static weight.
Option A: Fontsource (npm)
Fontsource packages every Google Font as an npm module with WOFF2 files and ready-made CSS.
npm install @fontsource-variable/inter
// In your app entry or root layout
import "@fontsource-variable/inter";
body {
font-family: "Inter Variable", system-ui, sans-serif;
}
The package includes unicode-range subsets, so the browser downloads only the scripts a page uses. Your bundler copies the files into your build output with hashed filenames.
Option B: Download from Google's CSS
You can ask the Google Fonts CSS API for a stylesheet and download the files it references. Google serves different formats to different user agents, so send a modern browser user agent to get WOFF2:
curl -s -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0 Safari/537.36" \
"https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" \
-o inter.css
grep -o "https://fonts.gstatic.com[^)]*" inter.css
The response contains one @font-face block per subset (latin, latin-ext, cyrillic, greek, vietnamese) with its unicode-range. Download the subsets you need and rewrite the URLs to your own paths.
Option C: The Source Repository
Every family's source files live in the google/fonts repository on GitHub, usually as TTF. Convert them to WOFF2 yourself and subset them, which gives you the most control:
pip install fonttools brotli
pyftsubset "Inter[opsz,wght].ttf" \
--unicodes="U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD" \
--layout-features="*" \
--flavor=woff2 \
--output-file="inter-latin.woff2"
That unicode range is the same "latin" subset Google uses. Our guide to font subsetting explains the options in detail.
Step 2: Write the @font-face Rules
Put the files in a public folder such as /fonts/ and declare them in your main stylesheet:
@font-face {
font-family: "Inter";
src: url("/fonts/inter-latin.woff2") format("woff2");
font-weight: 100 900;
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+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: "Inter";
src: url("/fonts/inter-latin-ext.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
body {
font-family: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
}
Key points:
font-weight: 100 900declares a weight range for a variable font. For static fonts, write one rule per weight with a single value.unicode-rangetells the browser to download the latin-ext file only if the page contains characters in that range.- Only WOFF2 is needed. Every browser that matters in 2026 supports it. You do not need WOFF, TTF, or EOT fallbacks.
- Keep the family name consistent across rules so the browser treats them as one family.
Step 3: Preload the Critical File
Preload only the file used by above-the-fold text, typically the latin subset of your body font:
<link rel="preload" href="/fonts/inter-latin.woff2" as="font" type="font/woff2" crossorigin />
The crossorigin attribute is required even for same-origin fonts, because fonts are always fetched in CORS mode. Without it, the browser downloads the file twice. Our guide to preloading web fonts covers when preloading helps and when it hurts.
Step 4: Serve with the Right Headers
Fonts should be cached for a long time and served with the correct MIME type.
| Header | Value |
|---|---|
Content-Type | font/woff2 |
Cache-Control | public, max-age=31536000, immutable |
Access-Control-Allow-Origin | Needed only if fonts are on a different origin, such as a CDN subdomain |
If your filenames do not include a version hash, add one (inter-latin.v4.woff2) whenever you update the font, so the immutable cache never serves a stale file.
For Netlify, add this to netlify.toml:
[[headers]]
for = "/fonts/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
Access-Control-Allow-Origin = "*"
For Nginx (recent versions already map .woff2 to font/woff2 in mime.types):
location ~* \.woff2$ {
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Access-Control-Allow-Origin "*";
}
Self-Hosting in Next.js
If you use Next.js, next/font/google already self-hosts. At build time it downloads the font files from Google, stores them with your static assets, and serves them from your own domain. No request goes to Google from the visitor's browser.
// app/layout.tsx
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({
subsets: ["latin"],
display: "swap",
variable: "--font-inter",
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={inter.variable}>
<body className="font-sans">{children}</body>
</html>
);
}
/* globals.css with Tailwind CSS v4 */
@import "tailwindcss";
@theme inline {
--font-sans: var(--font-inter), system-ui, sans-serif;
}
next/font also preloads the file, adds font-display, and creates a metric-matched fallback. For local files you have subset yourself, use next/font/local with a src path. If you are setting this up for the first time, our earlier walkthrough on adding a custom font to a Next.js project shows the basics.
Self-Hosting in WordPress
Since WordPress 6.5, the Font Library in the Site Editor can install Google Fonts. When you install a font through it, WordPress downloads the files into wp-content/fonts and serves them locally, so visitors never contact Google.
For block themes, you can also bundle fonts in the theme and register them in theme.json:
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {
"typography": {
"fontFamilies": [
{
"name": "Inter",
"slug": "inter",
"fontFamily": "Inter, system-ui, sans-serif",
"fontFace": [
{
"fontFamily": "Inter",
"fontWeight": "100 900",
"fontStyle": "normal",
"fontDisplay": "swap",
"src": ["file:./assets/fonts/inter-latin.woff2"]
}
]
}
]
}
}
}
Watch for plugins and page builders that still enqueue Google Fonts from the API. Check the Network panel for requests to fonts.googleapis.com after you switch; many themes have a setting to disable remote fonts.
Verifying the Switch
- Open DevTools, go to the Network panel, filter by "Font", and reload with the cache disabled. Every font should come from your domain.
- Filter all requests by "googleapis" and "gstatic". There should be none.
- Run Lighthouse and confirm there is no "Preconnect to required origins" suggestion for Google domains.
- Check the response headers on a font file for
font/woff2and the longCache-Controlvalue. - Compare Largest Contentful Paint before and after on a throttled mobile profile.
Self-Hosting Google Fonts FAQ
Yes. Google Fonts families are released under open licenses, mainly the SIL Open Font License and the Apache License, which allow downloading, embedding, and redistributing the fonts on any website, including commercial ones.
In most cases, yes. Browsers no longer share cached fonts across sites, so the CDN's main advantage is gone, while self-hosting removes extra DNS, TCP, and TLS setup for two Google domains. The gain is largest on mobile and on the first page view.
Only at build time, when it downloads the font files. The files are then served from your own deployment, so visitors' browsers never request anything from Google's font servers.
Only WOFF2. It is supported by every current browser and offers the best compression. Older formats such as WOFF, TTF, and EOT are only needed for very old browsers that are no longer in meaningful use.
Google occasionally updates fonts to fix glyphs or add languages. With Fontsource, update the npm package. With manually downloaded files, recheck the family's page or repository periodically and replace the files with new versioned filenames.
Not for the fonts themselves, because no visitor data goes to a third party. You may still need consent for other services such as analytics, embedded maps, or video players.
Conclusion
Self-hosting Google Fonts is one of the rare changes that improves performance and privacy at the same time. You drop two third-party connections, gain the ability to preload and cache files exactly as you want, and stop sending visitor IP addresses to Google for every page view.
The process is straightforward: get WOFF2 files (variable where possible), subset them by script, write @font-face rules with font-display and unicode-range, preload the critical file with crossorigin, and serve everything with a long immutable cache. On Next.js, next/font does it automatically; on WordPress, the Font Library handles it for you. Then verify in the Network panel that no request reaches Google's font servers.
Here are some useful references for going deeper on self-hosting fonts:
- web.dev: Best practices for fonts — self-hosting, preloading, and font-display guidance.
- Fontsource: Fontsource documentation — installing and importing self-hosted Google Fonts via npm.
- Next.js Docs: Font optimization — how
next/font/googleandnext/font/localself-host fonts. - Google Fonts: Google Fonts FAQ — licensing and usage terms for the Google Fonts library.
- MDN Web Docs: @font-face — every descriptor, including
unicode-rangeandfont-display.


