How next/font/google actually self-hosts
next/font/google downloads woff2 files at build time and serves them from your own origin. What that means for privacy and page speed.
Ansh Vasani
Co-Founder and CEO of Wend ·
One of the small things you stop thinking about until you run a security scan: where do your fonts come from?
The default answer most developers give is “Google Fonts.” That answer was correct in 2018 and quietly stopped being correct around 2022 when next/font/google shipped in Next.js 13. The API name looks like “use Google Fonts,” but what it actually does is download the fonts at build time and serve them from your origin. The Google CDN doesn’t enter the picture at runtime.
This is a small detail with three downstream wins worth knowing about. We’ll walk through what next/font/google does under the hood, why we leaned into it for Wend, and the things it lets you do with your CSP that you can’t do with the live Google CDN.
What next/font/google actually does
Concretely, when you write:
// src/app/layout.tsx
import { Fraunces, Inter, JetBrains_Mono } from "next/font/google";
const fraunces = Fraunces({
variable: "--font-fraunces",
subsets: ["latin"],
weight: ["400", "500", "600", "700", "800"],
style: ["normal", "italic"],
display: "swap",
});At build time, Next does four things:
- Fetches Google’s font metadata to figure out which woff2 files match the weights + styles + subsets you asked for.
- Downloads those woff2 files directly from Google’s CDN.
- Hashes the file names and stores them in your
.next/static/media/output, which Next serves under your origin at deploy time. - Generates a CSS
@font-faceblock that points to those self-hosted URLs.
The result is that at runtime, your visitors’ browsers fetch https://www.trywend.io/_next/static/media/<hash>.woff2, never anything on fonts.googleapis.com. The Google CDN is not in the critical path.
next/font/local, for fonts you ship as files in your repo. next/font/google is the Google-catalog convenience wrapper around the same machinery; it just handles the download for you. Both end up self-hosted. The difference is whether the source file lives in your git tree or gets pulled at build.Why we care
Three reasons, ranked by how often they actually matter.
1. CSP can be tightened to font-src 'self'
When you use Google Fonts via a <link> tag, your CSP has to allow fonts.googleapis.com and fonts.gstatic.com in both style-src and font-src. That’s two extra origins in your security policy that exist purely to fetch typography.
With self-hosted fonts, your CSP can read:
Content-Security-Policy:
default-src 'self';
font-src 'self';
style-src 'self' 'unsafe-inline';
...One fewer attack surface. Tighter policy. Cleaner audit reports. This matters more than it sounds if you’re pursuing CASA Tier 2, SOC 2, or any compliance review where someone scrutinizes your CSP. To be exact about our own position while we’re on the subject: Wend’s security assessment for Google’s restricted scopes is in progress, not finished, and we hold no SOC 2 report. What we can say is that the font question is one line of the review we did not have to argue about. Our security page keeps the current status.
2. SRI works (kind of)
The honest version of this one: Subresource Integrity on Google Fonts CSS is structurally impossible. Google generates a different CSS file per browser user-agent (because the woff/woff2/eot mix it serves depends on the browser), so the SHA-384 hash you’d need to pin changes per visitor. There’s no fixed hash to put in an integrity= attribute.
You can SRI the woff2 files themselves (those are stable), but not the CSS stylesheet that references them. If your security policy requires SRI on every subresource (and CASA does ask about this), Google Fonts via <link> can’t pass.
With self-hosted fonts via next/font, the woff2 files are served from your origin with the same 'self' trust as everything else. The stylesheet that references them is generated by Next at build time and lives in your CSS bundle, also under 'self'. The SRI question disappears.
3. Performance and privacy
Slightly more diffuse but still real:
- One fewer DNS resolution. Fetching fonts from your origin means the browser reuses the connection it already opened for the page. No separate
fonts.gstatic.comlookup, no extra TLS handshake. - Better preload control. Because the font files are in
.next/static/media/, Next can emit<link rel="preload">tags for them automatically. With Google Fonts you’d have to know the eventual woff2 URLs ahead of time, which you don’t. - No third-party IP exposure. Google stopped sending request IPs to fonts.googleapis.com servers for behavioral targeting a while ago, but you’re still making the request from the user’s IP, to Google’s edge. In the EU specifically, courts have at times taken issue with this. Self-hosting removes the question entirely. (For a German example: see the 2022 Munich Regional Court ruling on Google Fonts and GDPR.)
The whole config, end to end
For reference, the entire font setup in Wend’s src/app/layout.tsx:
import { Fraunces, Inter, JetBrains_Mono } from "next/font/google";
const fraunces = Fraunces({
variable: "--font-fraunces",
subsets: ["latin"],
weight: ["400", "500", "600", "700", "800"],
style: ["normal", "italic"],
display: "swap",
});
const inter = Inter({
variable: "--font-inter",
subsets: ["latin"],
weight: ["400", "500", "600", "700", "800"],
display: "swap",
});
const jetbrainsMono = JetBrains_Mono({
variable: "--font-jetbrains-mono",
subsets: ["latin"],
weight: ["400"],
display: "swap",
});
export default function RootLayout({ children }) {
return (
<html
lang="en"
className={`${fraunces.variable} ${inter.variable} ${jetbrainsMono.variable}`}
>
<body>{children}</body>
</html>
);
}The variable option exposes each font as a CSS custom property (--font-fraunces, --font-inter, --font-jetbrains-mono), which our Tailwind config picks up as the font-display, font-sans, and font-mono tokens. Adding a new font is one import + one variable + one Tailwind reference.
display: "swap" is the right default for almost every site. Text renders with the system fallback immediately, then swaps in the custom font when it’s ready. The alternative, display: "optional", is stricter (it never swaps, just keeps the fallback if the font doesn’t arrive in 100ms) and improves CLS slightly but can leave half your visitors looking at Arial. We chose swap.
The one catch
The build-time download means your build has to reach Google’s metadata endpoint. If your CI runs in an air-gapped environment, this fails closed. The workaround is either next/font/local with the woff2 files checked into the repo, or pre-warming a build cache that has the fonts already downloaded.
For Wend that’s not an issue. Vercel’s build environment has full outbound network. It’s worth knowing about if you’re building in a more locked-down CI.
Closing
The summary you can take to your next CSP review: modern Next.js apps using next/font/google do not have a runtime dependency on the Google Fonts CDN. The fonts are downloaded at build, served from your origin, hashed by Next, and accessible to font-src 'self'. The CDN is a build-time source, not a runtime resource.
This isn’t a heroic optimization. We’re leaning into the framework primitive. It’s still the kind of detail that catches you off guard in a security scan if you haven’t looked at where your fonts come from in a while. They probably come from your own origin already. If they don’t, three lines of configuration get you there.
Adjacent posts: our region architecture, and why we retired the EU half · auditing every AI inference