next.js: Automatic WebFont Optimization does not work

What version of Next.js are you using?

10.2.0

What version of Node.js are you using?

14.15.4

What browser are you using?

Chrome

What operating system are you using?

macOS BigSur

How are you deploying your application?

Vercel

Describe the Bug

The font style is not inlined even though I added a link to_document.tsx in the following format. (I’m sure using the Head component exported from next/document)

<Html lang="ja">
  <Head>
    <meta charSet="utf-8" />
    <link  href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP" rel="stylesheet" />
  </Head>
  <body>
    <Main />
    <NextScript />
  </body>
</Html>

I forked next.js to use a local next.js, and the same thing happened.

When I run ‘next build’, .next/server/font-manifest.json will be generated, but its contents will be empty. This probably happens only with certain fonts (maybe Japanese font?).

Also, if you run ‘yarn dev’ after ‘yarn build’, regardless of whether the font is Japanese or not, the .next/server/font-manifest. json was disappeared, is this a problem?

I found this issue(#19159), but it was already closed and did not solve the problem.

Expected Behavior

The tag is transformed as expected

  • link href property is replaced with data-href of the same value
  • style tag is created with the same data-href key/value
  • style tag contains an inlined font definition

The .next/server/font-manifest.json contains font definitions as fetched from https://fonts.googleapis.com/css

To Reproduce

  1. Build and start a server from my minimal repo:
git clone https://github.com/Co9xs/next-automatic-webfont-optimization-sample.git
cd next-automatic-webfont-optimization-sample
yarn 
yarn build
yarn dev
  1. Open http://localhost:3000/
  2. Inspect the <head> of the document
  3. Inspect the built .next/server/font-manifest.json file

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 21
  • Comments: 47 (14 by maintainers)

Commits related to this issue

Most upvoted comments

Still experiencing this with 12.1.6. Font optimization breaks in pages with getServerSideProps!

Same problem here. ~getInitialProps~ getServerSideProps seems to be causing the issue. Anywhere, not just in the _app.tsx page. (only in production, after running ‘build/start’)

To reproduce on a new project:

pages/_document.jsx

import { Head, Html, Main, NextScript } from "next/document";

const Document = () => {
  return (
    <Html>
      <Head>
        <link
          href="https://fonts.googleapis.com/css2?family=Inter&display=swap"
          rel="stylesheet"
        />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
};

export default Document;

pages/working.jsx

const WorkingPage = () => <>Working font here</>;

export default WorkingPage;

This page works as expected, it contains the html:

<style data-href="https://fonts.googleapis.com/css2?family=Inter&amp;display=swap">@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v7/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfMZs.woff) format('woff')}@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v7/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZJhjp-Ek-_EeAmM.woff) format('woff');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v7/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZthjp-Ek-_EeAmM.woff) format('woff');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v7/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZNhjp-Ek-_EeAmM.woff) format('woff');unicode-range:U+1F00-1FFF}@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v7/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZxhjp-Ek-_EeAmM.woff) format('woff');unicode-range:U+0370-03FF}@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v7/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZBhjp-Ek-_EeAmM.woff) format('woff');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v7/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZFhjp-Ek-_EeAmM.woff) format('woff');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v7/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZ9hjp-Ek-_EeA.woff) format('woff');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}</style>

pages/broken.jsx

const BrokenPage = () => <>Broken font here</>;

export default BrokenPage;

export async function getServerSideProps(context) {
  return {
    props: {},
  };
}

This page doesn’t load the font at all. The html contains:

<link rel="stylesheet" data-href="https://fonts.googleapis.com/css2?family=Inter&amp;display=swap"/>

I’m surprised there are not that many people experiencing this problem. I thought that a web-font in combination with getServerSideProps would be a pretty standard scenario 😃

This issue seems to have been fixed by "next": "12.2.0".

I just test the case above with:

  • getServerSideProps
  • <Head><link></Head>

We’re having the same issue here (on next 12.0.8). The workaround is to disable font optimization to get any google fonts to work in production build

// next.config.js

module.exports = {
  optimizeFonts: false,
}

Okay, I found what is causing the confusion, or maybe it’s a bug? Not sure. Maybe @timneutkens can tell us more about it?

So in the documentation of the font-optimalization they tell you to create a _document file. If you go to the _document documentation, they give you the following function example:

import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

However, if you look closely at the font optimization documentation, you see that you should probably be using the class component version:

import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head>
          <link
            href="https://fonts.googleapis.com/css2?family=Inter&display=optional"
            rel="stylesheet"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

After updating my document to a class component, everything worked as expected. If this is the way it is, the documentation of the font optimization might benefit from a warning or explanation about this.

Unfortunately, @twankruiswijk 's approach didn’t work for me as my Document component is already a class. For reference, it doesn’t work with this:

import React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import { ServerStyleSheet } from "styled-components";
import ServerStyleSheets from "@mui/styles/ServerStyleSheets";

class MyDocument extends Document {
  static async getInitialProps(
    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
    ctx
  ): Promise<{
    styles: JSX.Element;
    html: string;
    head?: JSX.Element[];
  }> {
    const styledComponentsSheet = new ServerStyleSheet();
    const materialSheets = new ServerStyleSheets();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) =>
            styledComponentsSheet.collectStyles(
              materialSheets.collect(<App {...props} />)
            ),
        });

      const initialProps = await Document.getInitialProps(ctx);

      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {materialSheets.getStyleElement()}
            {styledComponentsSheet.getStyleElement()}
          </>
        ),
      } as any;
    } finally {
      styledComponentsSheet.seal();
    }
  }

  render(): React.ReactElement {
    return (
      <Html lang="en">
        <Head>
          <link rel="preconnect" href="https://fonts.googleapis.com" />
          <link
            rel="preconnect"
            href="https://fonts.gstatic.com"
            crossOrigin=""
          />
          <link
            href="https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@300;400;500;700&display=swap"
            rel="stylesheet"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

I am encountering exactly the same problem with using Class and styled-component together inside getInitialProps, also unfortunately upgrading to next@12.1.4 does not fix the problem

I’m also having the same issue on Next 12.1.2.

I’m also experiencing this issue. Found a minimal setup that only requires:

Steps to reproduce:

  1. Run yarn create next-app
  2. In styles/global.css change font-family to Rubik
  3. In pages/index.js add this to the end:
export const getServerSideProps = async () => {
  return { props: {} };
};
  1. Add the following _document.js under pages/:
import Document, { Html, Head, Main, NextScript } from "next/document";

class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head>
          <link
            href="https://fonts.googleapis.com/css2?family=Rubik:wght@300;400;500;700&display=swap"
            rel="stylesheet"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;
  1. Run yarn build && yarn start
  2. Navigate to http://localhost:3000/
  3. Inspect and see how it uses the default font for everything instead of Rubik

Unfortunately, @twankruiswijk 's approach didn’t work for me as my Document component is already a class. For reference, it doesn’t work with this:

import React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import { ServerStyleSheet } from "styled-components";
import ServerStyleSheets from "@mui/styles/ServerStyleSheets";

class MyDocument extends Document {
  static async getInitialProps(
    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
    ctx
  ): Promise<{
    styles: JSX.Element;
    html: string;
    head?: JSX.Element[];
  }> {
    const styledComponentsSheet = new ServerStyleSheet();
    const materialSheets = new ServerStyleSheets();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) =>
            styledComponentsSheet.collectStyles(
              materialSheets.collect(<App {...props} />)
            ),
        });

      const initialProps = await Document.getInitialProps(ctx);

      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {materialSheets.getStyleElement()}
            {styledComponentsSheet.getStyleElement()}
          </>
        ),
      } as any;
    } finally {
      styledComponentsSheet.seal();
    }
  }

  render(): React.ReactElement {
    return (
      <Html lang="en">
        <Head>
          <link rel="preconnect" href="https://fonts.googleapis.com" />
          <link
            rel="preconnect"
            href="https://fonts.gstatic.com"
            crossOrigin=""
          />
          <link
            href="https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@300;400;500;700&display=swap"
            rel="stylesheet"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

NEWS! It was resolved with the Next.js update

Not sure if it was specifically addressed, but upgrading to Next.js 12.1.4 seemed to fix it!

Same problem here: the font does not get loaded and the html contains:

<link rel="stylesheet" data-href="https://fonts.googleapis.com/css2?family=Inter&amp;display=swap"/>

I am facing this issue only on pages with a dynamic route:

  • /pages/test.tsx will load the font as expected but
  • /pages/[slug].tsx will not load the font.

Both pages do not have any get***Props export.

It seems broken for the pages with getServerSideProps. For ISR pages with getStaticProps or even static pages with no data, they work perfectly.

const Page = () => (
  <>
    <NextHead>
      <link // eslint-disable-line
        href="https://fonts.googleapis.com/css2?family=Inter:wght@400..800&display=swap"
        rel="stylesheet"
      />
    </NextHead>
  </>
)

export async function getServerSideProps(context) {
  return {
    props: {},
  };
}

export default Page;

^ Currently using this hotfix: adding the <link> tag to your Next.js page with getServerSideProps

Doesn’t it make more sense to turn off the functionality?

Same issue: Next 12.1.6 and React 18.1.0. Custom _document is a React Class Component.

The most progress I’ve made is that it seems my elements are getting filtered out at/around here. It seems that isNodeCreatingLinkElement may be causing some of the issues people are having above, although I’m not sure that it is my issue exactly. I’ll have more time to research this weekend.

I hope this helps some people track down their issues!

Does it work sometimes, or it doesn’t inline always? Do you see any errors on the console during next build? I’m having a hard time reproducing the bug with the info provided. Any additional info is appreciated.

No, it never inlines, and there are no errors or warnings during next build.

I’m also using a Class component and next@12.1.4. The document page itself doesn’t have a getInitialProps but individual pages do have getServerSideProps.

@twankruiswijk Thank you, my problem with font optimization is solved.

Unfortunately the font still wasn’t included correctly. Turned out that for me the culprit was a key prop which I added because when running yarn dev I got the following warning:

Thank you. Removing the key prop fixed it for me too.

Ran into this issue as well using “next”: “11.0.1”.

I cleaned up my .babelrc and .eslintrc files according to the documentation:

.babelrc: https://nextjs.org/docs/advanced-features/customizing-babel-config .eslintrc: https://nextjs.org/docs/basic-features/eslint

Unfortunately the font still wasn’t included correctly. Turned out that for me the culprit was a key prop which I added because when running yarn dev I got the following warning:

Warning: Each child in a list should have a unique "key" prop. See https://reactjs.org/link/warning-keys for more information.
    at script
    at Head (webpack-internal:///./node_modules/next/dist/pages/_document.js:252:5)
class MyDocument extends Document {
  render(): JSX.Element {
    return (
      <Html>
        <Head>
          {/* This does not work, notice the key prop. Removing it fixed the issue for me */}
          <link
            key="does-not-work"
            href="https://fonts.googleapis.com/css2?family=Poppins:wght@700&display=swap"
            rel="stylesheet"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

Removing the key prop from the <link> element added the style element to the head of the document:

<head>
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@700&amp;display=swap" rel="stylesheet">
  <!-- ... -->
  <style data-href="https://fonts.googleapis.com/css2?family=Poppins:wght@700&amp;display=swap">@font-face{font-family:'Poppins';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v15/pxiByp8kv8JHgFVrLCz7V1g.woff) format('woff')}@font-face{font-family:'Poppins';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v15/pxiByp8kv8JHgFVrLCz7Z11lFd2JQEl8qw.woff2) format('woff2');unicode-range:U+0900-097F,U+1CD0-1CF6,U+1CF8-1CF9,U+200C-200D,U+20A8,U+20B9,U+25CC,U+A830-A839,U+A8E0-A8FB}@font-face{font-family:'Poppins';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v15/pxiByp8kv8JHgFVrLCz7Z1JlFd2JQEl8qw.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Poppins';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v15/pxiByp8kv8JHgFVrLCz7Z1xlFd2JQEk.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}</style>
</head>

It appears our issue is directly related to our .babelrc as well, so it’s safe to say that if you’re having font optimization issues: start there.

In our case, we were including the following plugin, which is already a part of babel/preset-env, which is already part of the next/babel preset.

[
    "@babel/plugin-transform-react-jsx",
    {
        "pragmaFrag": "React.Fragment"
    }
 ]