Mighil

why self-host fonts (woff2 & variable)

Yeah. This might be common knowledge to many, but hey..

I find myself explaining this to new collaborators fairly often, so I'm putting it together as an article I can point them to. It might be useful for you as well. Whether you’re a hobbyist web developer, a tinkerer, or simply curious about optimizing your site, the steps and reasoning outlined here are both practical and easy to follow.

The case for self-hosting

Google fonts issues:

Self-hosting means:

Why WOFF2 (and variable fonts)

WOFF2 is significantly smaller than other formats -- usually 30% smaller than regular WOFF. It's supported in all modern browsers, so there's no reason to keep serving older formats.

Variable fonts (when optimized) are even smarter. Instead of serving separate files for bold, italic, thin, etc., one variable font file handles all the weights and styles you need. This means fewer HTTP requests and a smaller total file size.

// Example
Regular WOFF: Inter-Regular.woff (50 KB)
Bold WOFF: Inter-Bold.woff (52 KB)
Total: 102 KB, 2 requests

Variable WOFF2: Inter-Variable.woff2 (65 KB)
Total: 65 KB, 1 request

Learn: How To Convert Variable TTF Font Files to WOFF2

Quick start to self-host your fonts

Plan A

Step 1: Get your fonts

Download variable fonts in WOFF2 format from sources like:

Step 2: Add the CSS

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-variable.woff2') format('woff2');
  font-weight: 100 900; /* variable fonts support a range */
  font-variation-settings: "wght" 400; /* optional: set default weight */
}

body {
  font-family: 'Inter', system-ui, sans-serif;
}

Step 3: Use font weights freely

.heading {
  font-weight: 700; /* bold */
}

.light-text {
  font-weight: 300; /* light */
}

Step 4: Optimize loading

Add this to your <head> to preload the font:

<link rel="preload" as="font" href="/fonts/inter-variable.woff2" type="font/woff2" crossorigin>

Plan B: If you don’t have the web space to host fonts

If hosting files yourself feels like extra overhead, Bunny Fonts gives you a Google Fonts mirror without the privacy baggage. It's still faster than Google's CDN in many regions.

<link rel="preconnect" href="https://fonts.bunny.net" crossorigin>
<link href="https://fonts.bunny.net/css?family=roboto:400,700" rel="stylesheet">

<style>
  body {
  font-family: 'Roboto', sans-serif;
  }
</style>

Font swap and fallbacks

When your custom font loads, the browser has to decide what to display while waiting. This is called font-display.

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-variable.woff2') format('woff2');
  font-display: swap; /* show system font immediately, swap when custom font loads */
}

font-display options:

For web fonts, swap is the move. Users see text immediately, then it switches when your font arrives. The UX is way better than waiting for blank space.

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-variable.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap; /* always use this */
}

body {
  font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

The fallback fonts (system-ui, -apple-system, etc.) are system fonts that look reasonable until your custom font loads. The browser uses the first one it recognizes.

Don't need custom fonts?

If you want a clean, simple stack that works everywhere without extra requests, check out Modern Font Stacks. It has pre-built combinations of system fonts that look great and have no loading penalty at all.

Example:

body {
  font-family: Charter, 'Bitstream Charter', 'Sitka Text', Cambria, serif;
}

TL;DR

That's it.

Tagged in tech