How to load (and preload) fonts

In the first version of our framework custom fonts weren’t supported but now we need to recommend the best way to do it, so we need to investigate this a bit :slight_smile:

This is an interesting article about when should fonts be loaded and how to use link preload to optimize the whole process:
https://andydavies.me/blog/2019/02/12/preloading-fonts-and-the-puzzle-of-priorities/

Feel free to comment or include more interesting articles or insights!

Any idea on what’s the best way to import local fonts?

I noticed that if I import the fonts and define the @font-faces inside <Global /> the fonts are requested twice: once on the initial load, and once on the hydration. And after that, every time <Global /> is rendered the fonts are requested again. It would be good to have a recommended way to do this.

I was thinking on moving the @font-face definitions outside of React, but I’m not sure what’s the right way to do that.

We aren’t doing anything different than what is done by Emotion, so I guess the best way to deal with fonts in Emotion will be the best way to deal with fonts in Frontity.

@orballo have you taken a look at how is this usually solved by people using Emotion (or even styled-components)?

I tried to load fonts but I can’t find a example to work with Frontity. Even If I try to load the font file located on assets/fonts/font.xxx it returns 404.

Can you write a code example?

I haven’t explored this further yet. The temporary solution I have is to render a <Global /> component with the @font-face definitions, but the problem here is that the fonts will be required twice on load, because of the hydration of React, causing some flickering.

Imports:

import { css, Global } from "frontity";
import LibreBaskerville from "../../fonts/LibreBaskerville-Regular.ttf";

Font face definition:

<Global
      styles={css` 
        @font-face {
          font-family: "Libre Baskerville";
          font-style: normal;
          font-weight: normal;
          font-display: fallback;
          src: url("${LibreBaskerville}") format("truetype");
        }
    `}
/>

The ideal solution I think is to load the fonts outside of the React cycle somehow. Not sure how to do this and I couldn’t find a quick solution around, need to explore it more.

It is important that you define the @font-face in a separeted <Global /> element because if you do this in an existing <Global /> element that might be using a state variable and is subject to rerender, the @font-face definition would be rerendered as well, asking again for the fonts and causing the flickering.

I tried this but I get the following error:

ERROR in ./packages/competicion-theme/src/assets/fonts/Metropolis-Bold.ttf 1:0
    Module parse failed: Unexpected character '' (1:0)
    You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
    (Source code omitted for this binary file)

Are you running the last version of frontity? The webpack's font loaders were added after the 1.0 so maybe you are missing that update? I didn’t run into that problem.

@orballo did you find out how people using Emotion outside of Frontity is dealing with the hydration process?

A solution for Frontity could be something like this:

{state.frontity.platform === "server" && <Global
      styles={css` 
        @font-face {
          font-family: "Libre Baskerville";
          font-style: normal;
          font-weight: normal;
          font-display: fallback;
          src: url("${LibreBaskerville}") format("truetype");
        }
    `}
   supressHydrationWarning
/>
}

I also wonder if this problem exists when using Emotion’s extractCritical, which manually moves all the CSS to the <head> on SSR. We used to do that, but I removed it to be able to have source-maps. Maybe we can add it back in production.

1 Like

Yes, thanks the problem was that! Now I have the fonts loaded!

2 Likes

I’ve done a quick test with TwentyTwenty and I cannot reproduce that problem. Fonts are only requested once for me.

This is the test:

const FontFace = ({ state }) => {
  // I added a dependency on state.router.link so this component is
  // rerendered each time it changes.
  state.router.link;
  // Now I do a console.log to check that component is being rendered
  // successfully on each state.router.link change.
  React.useEffect(() => {
    console.log("The FontFace component has been rendered");
  });

  return (
    <Global
      styles={css`
        @font-face {
          // ... 
        }
      `}
    />
  );
};

Am I doing something wrong? If you want to modify the test, this is the branch: https://github.com/frontity/frontity/tree/fontface-double-request-testing

I tried adding state.frontity.platform === "server" as I commented here but it was not needed, it worked fine without it.

@orballo once you confirm this please let me know!

I just checked this and realized that the problem exists in Firefox (and only if the cache is disabled), but not in Chrome. So… my bad! Thanks for looking into it though!

You’re right, it happens on Firefox: https://twentytwenty-theme-bbf22ygph.now.sh/

Thanks Eduardo. I’ll check if the state.frontity. platform === "server" trick works in Firefox.

No, it doesn’t, but that only happens if the Firefox DevTools are open and “disable cache” is selected, so I guess it’s not something we should worry about. I’m going to leave it as it is.

I agree.