19

Greetings Stack Overflow community! I'm using some custom fonts in my Next.js project using the following CSS:

@font-face {
  font-family: OpenSans;
  font-display: swap;
  src: url(./OpenSans-Regular.woff2) format('woff2');
}

The thing is that Lighthouse audit it's telling me to preload these .woff2 files but they are getting hashed by Next.js. I don't know how to add the link tag with rel="preload" for the fonts.

Where can I tell Next.js to preload these fonts?

Thanks you!

Daniel Ramos
  • 2,115
  • 3
  • 18
  • 28

3 Answers3

16

The problem is in Next.js, that the font files get compiled if you place them in any other folder besides public. So if you place them in the public folder and reference them manually, you can be using the link preload like below.

Include them in the Head tag and specify the type for the different font types for browser compatibility. Make sure to reference them correctly in the css as well.

<Head>
<link
        rel="preload"
        href="/fonts/yourfont/yourfont.woff2"
        as="font"
        crossOrigin=""
        type="font/woff2"
      />
</Head>
@font-face {
   font-family: "YourFont";
   font-style: normal;
   font-weight: 900;
   src: url('/fonts/yourfont.woff2') format("woff2"),
   url('/fonts/yourfont.woff') format("woff"),
   url('/fonts/yourfont.ttf') format("truetype"),
   url('/fonts/yourfont.svg#yourfont') format("svg");
}
Hendrik Vlaanderen
  • 808
  • 1
  • 7
  • 13
8

You can preload them in your _document.js inside the <Head> component by adding the following:

<link
  rel="preload"
  href="/fonts/inter-var-latin.woff2"
  as="font"
  type="font/woff2"
/>
nullspace
  • 870
  • 10
  • 12
  • 9
    The thing is that the output file name is hashed when the app is compiled :/ – Daniel Ramos Sep 01 '20 at 10:46
  • Anyone have a solution here? – Taylor A. Leach Mar 18 '21 at 18:27
  • @TaylorA.Leach Check my answer above - i hope it helps with the hashed solution when compiled – choz Mar 31 '21 at 10:07
  • @nullhook Would that work with `ttf` type as well? – Yaya May 18 '21 at 01:41
  • @DanielRamos the hash is calculated based on the file content, so if you use e.g. https://fontsource.org/ then you just need to ensure the version of your npm lib to be strict in `package.json`. E.g. `"@fontsource/lato": "1.0.0"`. This optimistically ensures that the link is forever constant. It can be changed e.g. when webpack changes its checksum algorithm or whatsoever, but this is highly unexpected and even this happens one day you'd need to adapt one URL in your 'head' section – smnbbrv Aug 31 '21 at 15:01
  • @TaylorA.Leach you can place them in the public folder, they won't get hashed and you can target them easily. Look above for my solution. – Hendrik Vlaanderen Nov 23 '21 at 14:37
7

For external fonts, you can simply add the preload link in your head component;

<link rel="preload" href="https://fonts.googleapis.com/css?family=Play&display=swap" as="font" />

As for the internal fonts that you have in your public folder, this requires;

  • file-loader configuration in next.config.js, this is to resolve the hashed filename
  • Type definition for the fonts
  • Then import it into your head

Which you can find my sample below;


I created a Layout.tsx which wraps my other components, which its main purpose is that there would not be duplicated meta tags - But this is unrelated to the question.

Here's the code I have in Next@10.0.5 - to preload both external and internal fonts.

App.tsx

function App({ Component, pageProps }: AppProps): JSX.Element {
  const queryClient = new QueryClient();

  return (
    <QueryClientProvider client={queryClient}>
      <Hydrate state={pageProps.dehydratedState}>
        <Provider store={store}>
          <main className="main-container position-relative" data-testid="main-container">
            <HeaderComp />
            <Layout>
              <Component {...pageProps} />
            </Layout>
            <FooterComp />
            <ScrollToTop />
          </main>
        </Provider>
      </Hydrate>
    </QueryClientProvider>
  );
}

Layout.tsx

import fontGilroyBold from "@assets/fonts/gilroy/Gilroy-Bold.woff";

const Layout: React.FunctionComponent = ({ children }) => {
  return (
    <div className="content font-play position-relative">
      <Head> {* next/head *}
        {/* ...metaTags */}
        <link rel="preload" href={fontGilroyBold} as="font" />
        <link rel="preload" href="https://fonts.googleapis.com/css?family=Play&display=swap" as="font" />
      </Head>
      {children}
    </div>
  );
};

next-env.d.ts

...
declare module "*.woff";
declare module "*.woff2";
...

next.config.js


const nextConfig = {
  webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
    /**
     * Override some of the existing loaders
     */
    // ...

    /**
     * Font imports in the code repository will be resolved as relative path
     */
    config.module.rules.push({
      test: /gilroy\/.+\.(woff|woff2)$/,
      use: {
        loader: "file-loader",
        options: {
          /** Stolen configuration from pre-existing "config.module.rules[2]" */
          outputPath: "static/fonts/gilroy/",
          publicPath: "/_next/static/fonts/gilroy/",
          limit: 1,
        },
      },
    });

    return config;
  },
};
choz
  • 17,242
  • 4
  • 53
  • 73
  • On importing fonts I am getting Cannot find module error. Any solution on how to resolve it? – Yash Vekaria Mar 21 '23 at 10:04
  • Hi @YashVekaria - can i get more context on that? but if it's related to typescript, most likely you may need to `declare module "*.woff"` in one of your `.d.ts` - This answer has been long outdated, I will also need to update it. – choz Mar 23 '23 at 23:30