How to Add Custom Local Fonts to Next.js and Vercel

Step 1: Add fonts to the public folder

I organize all my custom fonts inside ‘public/fonts/[font-family-name]’. For example, I use the fonts Lato and EB Garamond on my blog. My directory structure then looks like the follows:

  - fonts
    - EBGaramond
      - EBGaramond-Bold.ttf
      - EBGaramond-Regular.ttf
    - Lato
      - Lato-Regular.ttf
      - Lato-Bold.ttf
  - icons
  - images

We put fonts in the public folder because Next.js made the public folder special. Next.js serves static files, like fonts and images, under public folder in the root directory. Anything inside public folder can be referenced in your code with the base URL ‘/‘. For example, I can refer to Lato-Black.ttf with fonts/Lato/Lato-Black.ttf, instead of public/fonts/Lato/Lato-Black.ttf.

Step 2: In global.css, add custom font using @font-face

I prefer adding this CSS at-rule in global.css because it gives you access to the font anywhere on your pages. A bit about @font-face, it is a CSS at-rule which tells CSS to load download external fonts. It has several important descriptors that we will specify. To import EB Garamond in regular and medium, here is the code:

@font-face {
  font-family: "EBGaramond";
  src: url("/fonts/EBGaramond/EBGaramond-Regular.ttf");
  font-style: normal;
  font-weight: 400;
  font-display: swap;
@font-face {
  font-family: "EBGaramond";
  src: url("/fonts/EBGaramond/EBGaramond-Medium.ttf");
  font-style: medium;
  font-weight: 500;
  font-display: swap;
  • font-family: Specifies the name of font
  • src: Specifies where to get the font data. In the previous step we put the fonts in the ‘public’ folder. Because Next.js made the public folder special, we can refer to the font like such: public/fonts/Lato/Lato-Black.ttf with out the ‘public/‘ prefix.
  • font-style: Specifies explicitly the style of the font face. For example, if you font face has an italicized version, you can specify it to be italicized.
  • font-weight: specifies the thickness of the font. For example, font-style: normal corresponds to font-weight: 400; font-style: bold corresponds to font-weight: 700. Each font face has different font-weights one can choose at the time of download.
  • font-display: specifies how a font is displayed when the font is still downloading, and when it is ready to be displayed. It takes the following values:
    • auto: end user browser uses its default method
    • block: text will be an invisible placeholder while the custom font is loading; user sees no text until the custom font is fully loaded; AKA “flash of invisible text (FOIT)”
    • swap (recommended for maximum display of content + brand consistency): text will be displayed, but unstyled, until the custom font is fully loaded; AKA “flash of unstyled text (FOUT)”
    • fallback: a blend of block and swap. Text will be an invisible placeholder for around 100ms while waiting for the custom font to load. If after 100ms the custom font is not yet loaded, it will use unstyled fallback text. If the custom font is loaded in the next several seconds, it will swap the unstyled text with the custom font. If the custom font is not loaded in that short period of time, it will just use unstyled fallback text.
    • optional: like fallback, but if user’s connection is slow it will not attempt to load the custom font

Step 3: Preload your font in the tag of your page for optimal performance

import Head from "next/head";
import Link from "next/link";

export default function Layout {
    return (
            <body><Main /></body>

Why preload your font? WP Rocket wrote an amazing article with detailed explanation. Basically, it tells browsers to prioritize loading a resource such as custom fonts, so to prevent/reduce the instance of loading the rest of the website without the text because the font is not yet downloaded.