John Reilly MacGyver turned Dev 🌻❤️ TypeScript / ts-loader / fork-ts-checker-webpack-plugin / DefinitelyTyped: The Movie

Reduce cumulative layout shift in Docusaurus with fontaine

3 min read 932

Docusaurus Using Fontaine Reduce Cumulative Layout Shift

Custom font usage can introduce cumulative layout shift (or “jank”) to your website. This post shows how to use fontaine to reduce this with Docusaurus sites and will cover:

What is cumulative layout shift?

Cumulative layout shift (CLS) is a metric that measures the instability of content on a web page. It’s a Core Web Vitals metric.

You may well know it as “jank”. It’s jank that you see when a page loads and the text moves around, and it’s a big irritation. There’s a great description of it in this post on the topic; let me quote it here:

Have you ever been reading an article online when something suddenly changes on the page? Without warning, the text moves, and you’ve lost your place. Or even worse: you’re about to tap a link or a button, but in the instant before your finger lands—BOOM—the link moves, and you end up clicking something else!

This, in a nutshell, is what jank is — it’s very frustrating to end users and an issue that we as developers should try to avoid as best we can!

For the rest of this post, I will generally to refer to CLS as jank, as it’s a more relatable term.

What “jank” looks like

My blog uses a custom font called Poppins. Lovely though it is, using the font introduces jank to my site. It’s particularly noticeable on mobile phones. Here’s a gif of the jank in action:

My Jank

You see how the text shifts around as the custom font arrives? On the first line we either see:

  • The fallback font rendering four words on one line: “Bicep: Static Web Apps”


  • the custom font (Poppins) rendering three words on one line: “Bicep: Static Web”

It’s very noticeable—so noticeable that you can actually put a number on it.

The number is the CLS score which you can determine with Lighthouse. The CLS score is the sum of the layout shifts that occur on the page. The higher the score, the more jank there is. Cumulative Layout Shift was logged as 0.019 for the page above.

That’s not great.

I took some steps to reduce the issues experienced, such as font preloading, but the issues still remained — particularly on mobile networks where speed of loading is decreased and it takes a longer time for the custom font to load.

I had rather given up on improving things further. But then….

Introducing fontaine

One evening I was vaguely browsing Twitter when I came across a tweet from Daniel Roe that announced a new tool called fontaine:

Screenshot Tweet About Fontaine

I was intrigued. I wanted to try it out. I wanted to see if it could reduce the jank on my blog by using this tool.

Using fontaine with Docusaurus

I added fontaine as a dependency to my blog:

yarn add -D fontaine

I then cracked open my docusaurus.config.js file and wrote a small plugin to make use of fontaine:

const fontaine = require('fontaine');

// ...

/** @type {import('@docusaurus/types').Config} */
const config = {
  // ...

  plugins: [
    // ...
    function fontainePlugin(_context, _options) {
      return {
        name: 'fontaine-plugin',
        configureWebpack(_config, _isServer) {
          return {
            plugins: [
                fallbacks: [
                  'Segoe UI',
                  'Open Sans',
                  'Helvetica Neue',
                // You may need to resolve assets like `/fonts/Poppins-Bold.ttf` to a particular directory
                resolvePath: (id) => '../fonts/' + id,
    // ...
  // ...

This didn’t initially seem to make any difference, so I put it up as a work-in-progress PR and wrote up my findings as best I could. Daniel was kind enough to take a look. He uncovered two issues:

  • There was a bug in fontaine around how it handled CSS variables, for which he implemented a fix
  • Docusaurus uses custom fonts through the mechanism of CSS variables. This indirection confuses fontaine as it can’t read those variables. To accommodate this, we needed to update my CSS variable to add the override font family to the CSS variable:
-  --ifm-font-family-base: 'Poppins';
+  --ifm-font-family-base: 'Poppins', 'Poppins override';

Behind the scenes, there is a “Poppins override” @font-face rule that has been created by fontaine. By manually adding this override font family to our CSS variable, we make our site use the fallback “Poppins override” @font-face rule with the correct font metrics that finding generates.

It’s worth emphasizing that for the typical user of fontaine, this is not something they need to do. It’s only necessary for Docusaurus users because they use custom fonts through CSS variables. Using fontaine is very plug-and-play for most users.

Daniel was kind enough to raise a PR incorporating both the tweaks. When I merged that PR, I saw the following:

My Jank Fixed

Look at that! You can see the font loading, but there’s no more jumping of words from one line to another. It’s a huge improvement from before!


If you want to improve your CLS score, fontaine is a great tool.

This post demonstrates using it with Docusaurus, but please note that this is a generally useful tool that you can use with Vite, Next.js, and others — it’s not specific to Docusaurus.

Prior to using fontaine, my blog’s Cumulative Layout Shift was logged as 0.019. After incorporating it, the same score is logged as 0. This is good news!

I’m very grateful to Daniel for his help in getting it working with my blog. He went above and beyond, so thank you, Daniel!

In testament to what a great idea fontaine is built upon; in the time I’ve been writing this post, @next/font has been announced, which is based upon a similar idea.

Get setup with LogRocket's modern React error tracking in minutes:

  1. Visit to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.
  3. $ npm i --save logrocket 

    // Code:

    import LogRocket from 'logrocket';
    Add to your HTML:

    <script src=""></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • ngrx middleware
    • Vuex plugin
Get started now
John Reilly MacGyver turned Dev 🌻❤️ TypeScript / ts-loader / fork-ts-checker-webpack-plugin / DefinitelyTyped: The Movie

Leave a Reply