DEV Community

Cover image for 🖋Adding Fonts in Next.js (local fonts along with styled components)
Dharmi Kumbhani
Dharmi Kumbhani

Posted on

🖋Adding Fonts in Next.js (local fonts along with styled components)

Step 1 - Creating a Next.js application

I just like all my development folders to be on Desktop so i do cd Desktop

cd Desktop
npx create-next-app
//Running this script on the folder you want to store the project folder
* Will ask you whats your project name? projectName
/*
Enter the project name and it will create a folder with that name along 
with all the neccessary files
*/
Enter fullscreen mode Exit fullscreen mode

Step 2 - Adding custom fonts (local)

📁 pages

📁 public

⠀⠀⠀📁 fonts

⠀⠀⠀⠀⠀⠀fontName-style.woff

Step 3 - Preloading these fonts in _document.js.

Create _document.js file if you haven’t already and add the following code it preloads the font inside the Head.

If you are not using styled-component you should remove everything starting from static till render()

_document.js

import Document, {Html, Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
//--------------For styled-components only------------//
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet()
    const originalRenderPage = ctx.renderPage

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) =>
            sheet.collectStyles(<App {...props} />),
        })

      const initialProps = await Document.getInitialProps(ctx)
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        ),
      }
    } finally {
      sheet.seal()
    }
  }
//---------------------------------------------------//
  render() {
    return (
        <Html lang="en">
            <Head>
                <link
                  rel="preload"
                  href="/fonts/ProximaNova-Bold.woff"
                  as="font"
                  type="font/woff"
                  crossOrigin=""
                />
                <link
                  rel="preload"
                  href="/fonts/ProximaNova-Regular.woff"
                  as="font"
                  type="font/woff"
                  crossOrigin=""
                />
            </Head>
            <body>
                <Main />
                <NextScript />
            </body>
        </Html>
    );
}
}
Enter fullscreen mode Exit fullscreen mode
  • All that is important here is that tag inside Head.
  • PS: Html =/= html

    this 'Html" is common one for all the pages.

    It is recommended that for using title and meta you do it inside a specific page's

  • tag.
  • Also if you are not sure what all the code outside render means, i am using styled-component as my preferred choice for styling so that code is just for getting styled-components to work on Server side. Check out this blog to understand styled-components in Next.js for more details.

Extra:

Short answer: Yes, You can use both. They serve different purpose and can be used in the same application.

According to nextjs website:

Next.js uses the App component to initialize pages.To override, create the ./pages/_app.js file and override the App class

and

Pages in Next.js skip the definition of the surrounding document's markup. For example, you never include , 

, etc. To override that default behavior, you must create a file at ./pages/_document.js, where you can extend the Document class.

Note_document.js is only rendered on the server side and not on the client side. so event handlers like onClick is not going to work.

Step 4 - Declaring them globally

(again this is using style-components. Read more

create a _app.js file in your pages folder if its not already and add the . following:

import { createGlobalStyle } from "styled-components";

const GlobalStyle = createGlobalStyle`
    @font-face {
    font-family: 'ProximaNova-Regular';
    src: url('/fonts/ProximaNova-Regular.woff') format('woff');
    font-style: normal;
    font-weight: 400;
    font-display: swap;
  }
  @font-face {
    font-family: 'ProximaNova-Bold';
    src: url('/fonts/ProximaNova-Bold.woff') format('woff');
    font-style: bold;
    font-weight: 700;
    font-display: swap;
  }
`;

function MyApp({ Component, pageProps }) {
  return (
    <>
      <GlobalStyle />
      <Component {...pageProps} />
    </>
  );
}

export default MyApp
Enter fullscreen mode Exit fullscreen mode

Whats happening here?

In my global styles i am declaring @font-face and the source of each face is pointing to the url of the font-faces inside the font folder we just created.

  • Take care using @font-face, each font-family name is different and in accordance to the actual font file.
  • font-display is kept to swap.

Now here if you are interested in knowing what font display actually does Check out this article on CSS-tricks also this is helpful in understanding FOUT and FINT which you can read more about over here.

Thats it! Test it.

Now you add the following code inside index.js

index.js:

import Head from 'next/head'
// import styles from '../styles/Home.module.css'
import styled from  'styled-components';
const Test1 = styled.p`
  font-size: 40px;
  font-family: 'ProximaNova-Regular';
  color: #00688B;
`;
const Test2 = styled.p`
  font-size: 40px;
  font-family: 'ProximaNova-Bold';
  color: #00688B;
`;

export default function Home() {
  return (
    <div>
      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main>
        <Test1>Test1</Test1>
        <Test2>Test2</Test2>
      </main>
    </div>
  )
Enter fullscreen mode Exit fullscreen mode

Once this is added go to your terminal inside your folder and run:

$ yarn dev
// $ just mean run it inside your bash terminal you just need to copy "yarn dev"
Enter fullscreen mode Exit fullscreen mode

Once this is done go to https://localhost.3000 and see your next.js app running.

Local Host testing

Helpful Debugging:

Check for Safari developer tools and Google Chrome developer tools:

If you see a fonts folder in there and inside them if you can see font file. That most probably means your fontfiles have been successfully be served and it is good to go.

Safari:
Safari Debugging

Google Chrome:
Google Chrome Debugging

Important Links

  • Keep looking at this open source repository of Next.js actual site. https://github.com/vercel/next-site

    Specifically look at how they have declared fonts inside /styles/fonts.js and are calling it inside _app.js

  • If you observe closely there might be flashing of fonts happening in your project - Look more into FOUT (Flash of Unstyled Text) and FOIT (Flash of Invisible Text) https://leerob.io/blog/things-ive-learned-building-nextjs-apps

  • I even found this repository that uses the same libraries (next + styled components) you can have a look at it for any updates.

Some mistakes i made:

Before trying anything else i used a library called next-fonts and they had created a next.config.js file. However at the time of speaking the library is deprecated and you can directly use fonts without any library.

This file uses two libraries:

  1. next-fonts
  2. @zeit/next-css
const withCss = require("@zeit/next-css");
const withFonts = require("next-fonts");

module.exports = withFonts(
  withCss({
    webpack: (config, { isServer }) => {
      if (isServer) {
        const antStyles = /antd\/.*?\/style\/css.*?/;
        const origExternals = [...config.externals];
        config.externals = [
          (context, request, callback) => {
            if (request.match(antStyles)) return callback();
            if (typeof origExternals[0] === "function") {
              origExternals[0](context, request, callback);
            } else {
              callback();
            }
          },
          ...(typeof origExternals[0] === "function" ? [] : origExternals)
        ];

        config.module.rules.unshift({
          test: antStyles,
          use: "null-loader"
        });
      }
      return config;
    }
  })
);
Enter fullscreen mode Exit fullscreen mode

or just install @zeit/next-css

const withCSS = require('@zeit/next-css')

module.exports = withCSS({
  cssLoaderOptions: {
    url: false
  }
})
Enter fullscreen mode Exit fullscreen mode

The point of this file is to stop css modules to render and prevent any conflicts. But i personally haven't used it for my own site.

Links that helped me make this possible:

https://stackoverflow.com/questions/60841540/flash-of-unstyled-text-fout-on-reload-using-next-js-and-styled-components

https://codeconqueror.com/blog/using-google-fonts-with-next-js

https://leerob.io/blog/things-ive-learned-building-nextjs-apps

https://github.com/styled-components/styled-components/issues/3224

How to avoid layout shifts caused by web fonts

Discussion (0)