next.js: `@next/font` does not work in custom `pages/_document`

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 21.6.0: Mon Aug 22 20:19:52 PDT 2022; root:xnu-8020.140.49~2/RELEASE_ARM64_T6000
Binaries:
  Node: 18.12.1
  npm: 8.19.2
  Yarn: 1.22.19
  pnpm: N/A
Relevant packages:
  next: 13.1.2-canary.8
  eslint-config-next: 13.1.2
  react: 18.2.0
  react-dom: 18.2.0

warn - Latest canary version not detected, detected: “13.1.2-canary.8”, newest: “13.1.2”. Please try the latest canary version (npm install next@canary) to confirm the issue still exists before creating a new issue. Read more - https://nextjs.org/docs/messages/opening-an-issue

Which area(s) of Next.js are affected? (leave empty if unsure)

Font optimization (@next/font)

Link to the code that reproduces this issue

WIP

To Reproduce

Define a font Create a custom document at pages/_document.tsx Import the fonts and apply the variables to the <Html> Next element

Describe the Bug

Classes have no variable definition. When applied further down the dom (IE to a component or page) they do have a definition.

Expected Behavior

Classes would have a definition and apply CSS variables properly when applied to the root HTML element.

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Reactions: 5
  • Comments: 16 (2 by maintainers)

Most upvoted comments

I understand i don’t need to import it in layouts since app is rendered on every page. But I don’t want a wrapper around everything on every page just to add a font. So the alternative would be to apply it to some existing markup, which would only exist in our layouts. For now, I’ve gone with a CSS variable workaround like the following:

import "../styles/globals.css"
import type { AppProps } from "next/app"
import { Sans } from "../fonts"

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <style jsx global>{`
        :root {
          --font-sans: ${Sans.style.fontFamily};
        }
      `}</style>
     <Component {...pageProps} />
    </>
  )
}

and then define the font family on the HTML element in globals.css html { font-family: var(--font-sans); }. I guess we’ll come up with a cleaner solution to this when the app folder is finalized and we can migrate over.

@PeterKottas thanks for pointing out the dependence on styled-jsx I didn’t actually know what those attributes were doing. I removed that and it shaved about ~2kb, so not hugely significant but worth removing since we aren’t using it anywhere else. What I now am using:

import "../styles/globals.css"
import type { AppProps } from "next/app"
import { Sans } from "../fonts"

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <style dangerouslySetInnerHTML={{ __html: `
        :root {
          --font-sans: ${Sans.style.fontFamily};
        }`}} />
     <Component {...pageProps} />
    </>
  )
}

It is probably fine to apply the font family as you have there, I was just worried about specificity issues having that font family defined in style tag vs all of our other styles coming from stylesheets - I think you could do the same to remove the styled JSX usage with dangerouslySetInnerHTML.

import { barlow } from "@/fonts";

import { useIsomorphicLayoutEffect } from "react-use";

export default function App({ Component, pageProps }) {
  useIsomorphicLayoutEffect(() => {
    document.body.style.setProperty("--font-family", barlow.style.fontFamily);
    document.body.className = cn(barlow.className, barlow.variable);
  }, []);

  return <Component {...pageProps} />;
}

@alex-pominov solution worked for me, but I had to add on className too

Google fonts does not seem to be working at all in app.

In our project we also had to set font variable at body level, to cover modal and popover styles that renders in portals. That’s what I using:

// _app.tsx

import "../styles/globals.css"
import { Sans } from "../fonts"
import type { AppProps } from "next/app"
import { useIsomorphicLayoutEffect } from 'react-use'

export default function App({ Component, pageProps }: AppProps) {

   useIsomorphicLayoutEffect(() => {
    document.body.style.setProperty('--font-family', Sans.style.fontFamily);
  }, []);
  
  return <Component {...pageProps} />
}

Try using pages/_app.tsx not /pages/_document.tsx 🙏🏼

Learn more here: https://nextjs.org/docs/basic-features/font-optimization#google-fonts

don’t belive it’s supposed to

Good tip about the preload attribute! I totally missed that. Thanks. Now I am quite happy with the style approach. The only change I made compared to your version is I wrapped the style attribute with the next Head element. I don’t think it’s necessary these days, but I still think it’s best practice and it could theoretically prevent FOUT even further.