Skip to content
Frontsiter

Self-host web fonts without layout shift

Serve fonts from your own domain, load them fast, and stop the text from jumping when they arrive.

By · Published · 2 min read

The problem

Your web font makes pages slower, and the text jumps when the font loads. There are two causes. The browser has to fetch the font from another server, such as Google Fonts. And the fallback font has a different size from the web font, so the text reflows when the web font arrives.

It is the quickest to add, but every visit has to contact Google before the text can show. That is slower, and it sends the visitor's IP address to Google. Self-hosting removes both problems.

The fix

1. Get the font as one WOFF2 file. Choose a variable font, so one file covers every weight, and only the latin characters if your site is in English. Fontsource (fontsource.org) offers the Google Fonts library ready for self-hosting.

2. Load it from your domain and preload it. Put this tag in the <head>. Fonts need the crossorigin attribute, even on your own domain:

html
<link rel="preload" href="/fonts/ibm-plex-sans-latin.woff2" as="font" type="font/woff2" crossorigin />

3. Add a fallback font with the same size. This is what stops the jump. The values below match Arial to IBM Plex Sans; tools such as Fontaine and Capsize calculate them for any font:

css
@font-face {
  font-family: "IBM Plex Sans";
  src: url("/fonts/ibm-plex-sans-latin.woff2") format("woff2");
  font-weight: 100 700;
  font-display: swap; /* show text at once in the fallback font */
}

@font-face {
  font-family: "IBM Plex Sans Fallback";
  src: local("Arial");
  size-adjust: 101.17%;
  ascent-override: 101.32%;
  descent-override: 27.18%;
  line-gap-override: 0%;
}

body {
  font-family: "IBM Plex Sans", "IBM Plex Sans Fallback", sans-serif;
}

Safari ignores the three -override lines, so a small shift in line height can remain there.

In Next.js

next/font does all three steps when you build the site. Call it once in app/layout.tsx:

tsx
import { IBM_Plex_Sans } from "next/font/google";

const plexSans = IBM_Plex_Sans({ subsets: ["latin"], variable: "--font-plex-sans" });

// In the root layout: <html lang="en" className={plexSans.variable}>

Check that it works

  • Network tab: no requests to fonts.googleapis.com or fonts.gstatic.com.
  • Lighthouse: Cumulative Layout Shift (CLS) is 0, or close to it.

Frontsiter works this way: one 40 KB font file, preloaded and served from frontsiter.com, with no requests to Google.

Questions or ideas?

Corrections and topic ideas are always welcome. Email hello@frontsiter.com.

Contact us