How to avoid render-blocking fonts by self-hosting a variable font
The classic way to add a custom font is to paste a <link> to Google Fonts in your <head>. It works, but it quietly slows your page down and hands a third party some control over your rendering. On my portfolio I self-host JetBrains Mono instead — one import line, bundled by Vite, and no external request. Here’s why that’s faster and how to do it.
Why the Google Fonts link is render-blocking
The usual snippet looks like this:
<!-- The render-blocking pattern -->
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap" rel="stylesheet" />A <link rel="stylesheet"> in the <head> is render-blocking — the browser will not paint the page until it has fetched and processed that stylesheet. And here it’s worse than a normal local stylesheet, because of the chain of requests hiding behind that one line:
sequenceDiagram participant B as Browser participant G as fonts.googleapis.com participant S as fonts.gstatic.com B->>G: 1. DNS + connect + fetch CSS G-->>B: CSS with @font-face URLs B->>S: 2. DNS + connect + fetch .woff2 S-->>B: font file Note over B: only now can text render in the real font
That’s two separate third-party domains, each needing a DNS lookup, a TCP connection, and a TLS handshake before a single byte of font arrives. On a slow connection those round-trips add up, and until then your text either doesn’t show or shows in a fallback and then jumps (layout shift).
The fix: install the font and import it
Fontsource packages open-source fonts as npm modules. I install the variable version of JetBrains Mono and import it once in my app entry point:
// src/main.tsx
// Self-hosted variable font — no render-blocking request to Google Fonts.
import "@fontsource-variable/jetbrains-mono"
import "./index.css"That single import is the whole thing. Because it’s a plain module import, Vite (my bundler) sees it at build time, pulls the @font-face CSS and the .woff2 file into my own build output, and serves them from my own domain. No fonts.googleapis.com, no fonts.gstatic.com, no extra DNS lookups.
Why “variable” matters
A variable font packs a whole range of weights (light through bold) into one file, instead of shipping a separate file per weight. So @fontsource-variable/jetbrains-mono gives me every weight I might use from a single download — smaller total payload and fewer requests than grabbing four static weights.
What self-hosting actually buys you
- No third-party round-trips. The font is served from the same origin as everything else, so it reuses the connection you already opened for your HTML and JS.
- The bundler controls caching and hashing. Vite fingerprints the font file, so it caches aggressively and busts correctly on change.
- Privacy and reliability. You’re not depending on Google’s servers being up or on their privacy terms.
- It works offline / behind firewalls. Handy for intranet or PWA use.
Since my font ships inside the CSS my page already loads, I reference it through a variable in that CSS and use it everywhere the terminal aesthetic needs monospace:
/* the font is available as a normal family once imported */
font-family: var(--font-mono, monospace);A note for prerendered apps
My site is statically prerendered with vite-react-ssg, and this approach fits that perfectly: the font import is resolved at build time, so the generated bundle already references a local, hashed font file. There’s no runtime request to a font CDN happening during or after hydration — the asset is just part of the site.
What to remember
- A Google Fonts
<link rel="stylesheet">is render-blocking and adds two extra third-party domains’ worth of round-trips. - Self-host with Fontsource:
import "@fontsource-variable/jetbrains-mono"in your entry file and let the bundler include it. - Prefer the variable build — one file covers all weights, so fewer/smaller requests.
- Self-hosting means same-origin delivery, bundler-controlled caching, better privacy, and no external dependency.
- Reference the family in CSS as usual (
var(--font-mono, monospace)) once the import is in place.
A monospace font like JetBrains Mono is also central to the CRT-terminal look of the portfolio it was chosen for.