fontsource / fontsource

Self-host Open Source fonts in neatly bundled NPM packages.
https://fontsource.org
MIT License
4.96k stars 174 forks source link

Preload Fonts #83

Closed alexnault closed 6 months ago

alexnault commented 3 years ago

Using fontsource, how can one preload a font as to prevent a flash of faux-text?

import React from "react";
import { Helmet } from "react-helmet";
import "fontsource-inter";

export default function App() {
  return (
    <Helmet>
      <link
        rel="preload"
        as="font"
        href={/* ? */} // <----------
        type="font/woff2"
      />
    </Helmet>
  );
}

This issue is similar to another issue faced by typeface.

ayuhito commented 3 years ago

I'd assume we'd have to create our own React Helmet component and ship them with each package? Simply pass in values such as the weight etc. We'd then be able to set the filepath and Webpack would handle the rest.

Would this be applicable to Vue or other frameworks?

SalahAdDin commented 3 years ago

I found the same problem at testing web vitals: image Other fonts have no problem but this one affects the load time; how can we handle this kind of event?

ayuhito commented 3 years ago

Here's a barebones simple draft of the React Helmet Component I had in mind that we could export with each package. The props arrangement will likely be different, but the following is just something to roughly illustrate the approach.

function FontsourcePreload(props) {
    return <link rel="preload" href="files/{props.fontFileName}.woff2" as="font" type="font/woff2" crossorigin>
}

I'm not sure if this approach would work. It's not something I've gotten around to test, but it's the only solution I have in mind that logically MIGHT makes sense. If there are other approaches to this though, I would be more than happy to look into it.

Alternatively, to resolve the FOUT issue - https://dev.to/fyfirman/how-to-fix-fout-flash-of-unstyled-text-in-react-1dl1

albv commented 2 years ago

As a workaround you can do something like this

import React from "react";
import { Helmet } from "react-helmet";

import '@fontsource/montserrat/latin-500.css'
import '@fontsource/montserrat/latin-700.css'

import montserrat500 from '@fontsource/montserrat/files/montserrat-latin-500-normal.woff2'
import montserrat700 from '@fontsource/montserrat/files/montserrat-latin-700-normal.woff2'

export default function App() {
  return (
    <Helmet>
      <link rel="preload" as="font" href={montserrat500} type="font/woff2" />
      <link rel="preload" as="font" href={montserrat700} type="font/woff2" />
    </Helmet>
  );
}
ayuhito commented 2 years ago

@albv, thank you for the suggestion. Quite frankly, I think this should be the proper step forward rather than be treated as a 'workaround'.

I think it'd be fair to add this method in the official documentation.

GuiSim commented 2 years ago

Ran into this issue with NextJS today. Importing woff2 doesn't work out of the box.

teauxfu commented 2 years ago

I am struggling to get this to work in the way I expect.

I set up this minimal repo using Next, Fontsource, and the next-fonts "plugin" to import woff2 directly into the JS.

https://github.com/teauxfu/next-fontsource-test

image


The resource http://localhost:3001/_next/static/chunks/fonts/montserrat-latin-500-normal-ade7985dfab42940651537039e999ad9.woff2 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally.

It seems like the font does preload (loads twice, in fact 😞), but is not being used. SO has several posts around the warning appearing in Chromium, but I didn't see anything immediately helpful after a cursory look around.

darkkatarsis commented 2 years ago

Unfortunately, the above suggestion does not solve the problem. Both fonts are loaded, but the preload one is not used.

was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate as value and it is preloaded intentionally.

SoftCreatR commented 2 years ago

The problem is, that you are attempting to preload to preload the original file URL ([...]/montserrat-latin-500-normal.woff2), but not the hashed one ([...]/montserrat-latin-500-normal-ade7985dfab42940651537039e999ad9.woff2).

I ended up doing everything myself. While the fontsource package is pretty neat, preloading and font-display are somewhat inflexible, or at least hard to implement, compared to putting a file in the public folder, and adding a small piece of CSS:

@font-face {
    /* Copyright: Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) */
    font-family: 'Inter';
    font-style: normal;
    font-weight: 400;
    font-weight: 100 900;
    src: url(/font/families/Inter/Inter.woff2) format('woff2 supports variations'), 
         url(/font/families/Inter/Inter.woff2) format('woff2-variations'),
         url(/font/families/Inter/Inter.woff2) format('woff2');
    font-display: swap;
}
ayuhito commented 2 years ago

I ended up doing everything myself. While the fontsource package is pretty neat, preloading and font-display are somewhat inflexible, or at least hard to implement

Hopefully, #234 addresses some of those flexibility concerns you may be facing.

SoftCreatR commented 2 years ago

I ended up doing everything myself. While the fontsource package is pretty neat, preloading and font-display are somewhat inflexible, or at least hard to implement

Hopefully, #234 addresses some of those flexibility concerns you may be facing.

Maybe. But the preloading issue remains unresolved, as it currently requires a loader to be configured (webpack/next config), and further steps, to preload the correct file/url. Doing everything manually takes roughly two minutes :) And it doesn't produce any overhead.

ayuhito commented 2 years ago

But the preloading issue remains unresolved, as it currently requires a loader to be configured (webpack/next config), and further steps, to preload the correct file/url.

If the outdir is configured to the public directory, it's pretty much the same as doing it manually. Preloading framework agnostically would be possible since nothing gets bundled and hashed.

Doing everything manually takes roughly two minutes :) And it doesn't produce any overhead.

True. That's always been the case for anyone who wanted to self-host. The only difference at that point is the ease of installing and updating packages via NPM, which still seems to be the preferable option for a lot of developers and I don't see that changing.

SoftCreatR commented 2 years ago

If the outdir is configured to the public directory, it's pretty much the same as doing it manually. Preloading framework agnostically would be possible since nothing gets bundled and hashed.

Until now, I wasn't able to make it work properly, and I haven't found a working example, based on a newer next.js version :/

ayuhito commented 2 years ago

Until now, I wasn't able to make it work properly, and I haven't found a working example, based on a newer next.js version :/

I meant for when #234 is finished, then it would be an option ^^

JacobCaron commented 2 years ago

I don't have a solution but I also wanted to point out that this makes it difficult for Canvas / Context2D users to use fontsource as the font is stored as a bitmap, so we need to know when the font has loaded in order to proceed. As far as I can tell, there is no preloading or event handlers that help with this case..

sanjipun commented 2 years ago

As a workaround you can do something like this

import React from "react";
import { Helmet } from "react-helmet";

import '@fontsource/montserrat/latin-500.css'
import '@fontsource/montserrat/latin-700.css'

import montserrat500 from '@fontsource/montserrat/files/montserrat-latin-500-normal.woff2'
import montserrat700 from '@fontsource/montserrat/files/montserrat-latin-700-normal.woff2'

export default function App() {
  return (
    <Helmet>
      <link rel="preload" as="font" href={montserrat500} type="font/woff2" />
      <link rel="preload" as="font" href={montserrat700} type="font/woff2" />
    </Helmet>
  );
}

I have absolutely no idea how you are able to import this line. Gives error on typescript.

> import montserrat500 from '@fontsource/montserrat/files/montserrat-latin-500-normal.woff2'
> import montserrat700 from '@fontsource/montserrat/files/montserrat-latin-700-normal.woff2'
ianschmitz commented 1 year ago

For those that have an index.html that you control such as when using Vite or similar you can simply add the preload to the page. For example when using the Lexend font:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
        <link
            rel="preload"
            as="font"
            crossorigin="anonymous"
            href="/node_modules/@fontsource/lexend/files/lexend-latin-variable-wghtOnly-normal.woff2"
            type="font/woff2"
        />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>My page</title>
    </head>
    <body>
        <div id="root"></div>
        <script type="module" src="/src/main.tsx"></script>
    </body>
</html>

And then in main.tsx:

// Import the font before anything else
import "@fontsource/lexend/variable.css";
Stevie-Ray commented 1 year ago

Couldn't find any help for Vue users and it's kinda similar, but it might still help someone!

import { useHead } from '@unhead/vue'

import '@fontsource/roboto/latin-400.css'
import '@fontsource/roboto/latin-500.css'
import '@fontsource/roboto/latin-700.css'

import roboto400 from '@fontsource/roboto/files/roboto-latin-400-normal.woff2'
import roboto500 from '@fontsource/roboto/files/roboto-latin-500-normal.woff2'
import roboto700 from '@fontsource/roboto/files/roboto-latin-700-normal.woff2'

useHead({
  link: [
    {
      rel: 'preload',
      href: roboto400,
      as: 'font',
      crossorigin: 'anonymous',
      type: 'font/woff2'
    },
    {
      rel: 'preload',
      href: roboto500,
      as: 'font',
      crossorigin: 'anonymous',
      type: 'font/woff2'
    },
    {
      rel: 'preload',
      href: roboto700,
      as: 'font',
      crossorigin: 'anonymous',
      type: 'font/woff2'
    }
  ]
})
flying-sheep commented 10 months ago

If you use a bundler (which most of you probably do), there’s an easy solution: https://github.com/cssninjaStudio/unplugin-fonts

SalahAdDin commented 10 months ago

If you use a bundler (which most of you probably do), there’s an easy solution: https://github.com/cssninjaStudio/unplugin-fonts

Is not enough the current font package?

flying-sheep commented 10 months ago

It’s enough if you’re confident you can add and manually maintain the preload tags.

I suggested the plugin as a way to have the preload tags inserted automatically.