FOIT, FOUT, and google fonts in Gatsby

10-27-2020

I just want to talk something about fonts / web fonts today, since in the last version of this blog, if you newly load the page, you will see a flash of unstyled text (which is exactly FOUT! But not so simple) for a second and it made me think why the page has a "loading problem" and if Gatsby is that good in performance like advertised.. (I know I'm naïve, forgive me..)

If you have experience of google fonts, you will know it provides different ways to use:

  1. You can link it into your html as a "stylesheet" or "@import" it to your CSS via a powerful google CDN:

    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@500&display=swap" rel="stylesheet">
  2. Or you can download the whole family to your local and host it yourself.

You may find the first URL is actually an URL of a stylesheet and not the font itself. It contains a bunch of @font-face declarations. You can also use a link to download a font stack (like me https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;700&family=Open+Sans:wght@400;700&family=Ubuntu:wght@400;700). If you go deep into the @font-face declarations, you will find they are using URLs like https://fonts.gstatic.com/s/roboto/v20/KFOlCnqEu92Fr1MmEU9fABc4AMP6lbBP.woff2 as source. That means if you use embedded link for google fonts, you actually at least do requests twice. Why google does this is that the fonts will be regularly updated and obsoleted, so it is dangerous to link the font files directly into your web page. The second thing is the font-familys are predefined in the stylesheets, and also with unicode-range: that is important for localized versions to have a safe character mapping and smart dynamic download to improve the performance.

That is definitely an obstacle for you to have seamlessly preloaded fonts by page loading because of the 2-requests-workaround. I tried a lot and did some investigations to force the fonts to be preloaded or prefetched with the first approach of embedded link. The fact is: it cannot. There was a plugin for Gatsby (gatsby-plugin-prefetch-google-fonts) helping you, but it is already deprecated and becomes un-usable. It used to prefetch the real font files from the google API, actually, during the build time - the risk mentioned above still exists, and the it needs to be maintained actively to check if there are some fonts unavailable.

One better way to bring preload to the font loading is: to use the second approach. Download the font files, create @font-faces in your self-hosted stylesheets, and link the font files with rel="preload" and as="font". The trade-off is that you have a larger bundle to load - and I want to take advantage of the google CDN :P . If so, you have to accept the fonts from google API cannot be preloaded, sorry. But what you can at least do! - you can set preconnect to the source of the font files - fonts.gstatic.com, and that will at least accelerate your download timing a little:

<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin></link>

But google at least gives you an opportunity to choose between FOIT and FOUT - flash of invisible text, or flash of unstyled text. According to the "content first" principle, the latter one should be prefered, and better for SEO. But sometimes you won't accept to have unstyled pages for hundred milliseconds - for the designer blogs, for example. Then FOIT could be the choice, to only show the texts once the custom font is loaded successfully. In the link of google font, you will find display=swap: that is the font-display attribute in a font face. It defines how the font will be treated from the stack loading process:

  • swap: FOUT, the characters will be shown with a browser basic font style, and be swapped to beautiful ones once they are loaded;
  • block: FOIT, default for most browsers, as I explained;
  • fallback: something between swap and block, the rendering waits for loading the custom font for a short time, and do swap if it is not finished loading;
  • optional: like fallback, also waits for a short time, but uses a fallback font instead without swap if the custom font is not available;
  • and surely, auto.

For example, I use the 'material icon' fonts in this site, which is quite large for loading. With display=swap, you may see the icons shown by its name text instead for a very short while. But with display=block, the icons will be hidden until the font is loaded - it could be risky, and I really want to make sure the google CDN connections are not banned from some countries :)

So far about the google font itself.. Since I trust google CDN, the page loading process with delayed text style should have another reason, from my fault - like every issue, always blame yourself to find a solution (and just start blaming others if you are really desperate / want to be a desperado 😂 ) After a long detective work, I finally found two:

  1. the stupid one comes first: I use react helmet and put it into the gatsby-browser.js and gatsby-ssr.js to generate the html head. I just create links to the google fonts in this way:

    <Helmet>
    <!-- ... -->
    <link
        href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;700&family=Open+Sans:wght@400;700&family=Ubuntu:wght@400;700&display=swap"
        rel="stylesheet"
    ></link>
    <link
        href="https://fonts.googleapis.com/icon?family=Material+Icons&display=swap"
        rel="stylesheet"
    ></link>
    <!-- ... -->
    </Helmet>

    And I noticed the stylesheets are loaded at a late time point and have helmet.js as initiator. That should definitely wrong! That is not what Gatsby promises to do! A html head should definitely be load at a very early time and not dynamically set by a js file. And I just noticed I forgot to have the gatsby-plugin-react-helmet! That is a very important one to let Gatsby generate static helmet at build time! Otherwise the helmet just exists in a section of dynamic scripts. :)

  2. The same principle is also powerful for loading global styles. I have a GlobalStyle component generated from global-style.tsx with styled-component by its createGlobalStyle method, and imported it into the helmet. It generates a CSS-in-JS solution by Gatsby, actually. I know, every frontend developer likes JS and hates CSS at some age (so did I 😬 ) --> That is totally fine and reasonable, when you create styles for a component. You then have a scoped component which can take variables from the component property conveniently, and there is no need to have this style far before the component is loaded. But for global stylesheets, that could be different. Sometimes we have critical styles that we want to load them at very beginning. Like the problem I had, although you have the right fonts showing, the colors and font sizes are not loaded correctly at very beginning and a flash still exists. I finished with creating a base.css (quite short, only for the most critical styles) and load it in the helmet, or you can just import it in the gatsby-browser.js and gatsby-ssr.js. They will be built into static and ready-to-serve.

During the time I'm working I really have a better and better feeling to the CSS language. I think it is powerful and good for webpage performance. The unchangeable global scope could be definitely as issue.. And the most critical issue could be that it is supported by different browsers very differently.. But just want to put a note to myself: don't hate a thing in total and deny it for every scenario, and to a person the same :)

© 2021 Shan