TailwindCSS with @next/font

Here's how to integrate the new @next/font in Next.js 13 with TailwindCSS.

Published 30 October 2022 - Updated 30 October 2022 - by Alvar Lagerlöf

If you're looking into and trying out the new Next.js 13, you might have noticed @next/font. If you haven't, it's a way to optimize local and external fonts in Next via various advanced techniques. The good thing is they did it for us, and it works!

But as I started thinking about trying it out, I noticed that the docs didn't provide any guidance on how to integrate with Tailwind yet, which I use for my site. I realised that I needed to do some tweaking to make it works smoothly. Without further ado, here's what I did.

Edit: Since writing this, I found out that the same setup also works well with the ./pages directory. I have added a section on how to use that below.

Previous setup

My previous setup defined fonts like this:

// tailwind.config.js

module.exports = {
  ...
  theme: {
    ...
    extend: {
      fontFamily: {
        heading: ["MADE Dillan", "sans-serif"],
        subheading: ["Space Text", "sans-serif"],
      },
    }
  }
}
/* ./style/global.css */

@tailwind base;
@tailwind components;

@font-face {
  font-family: "MADE Dillan";
  src: url(/fonts/made-dillan.woff) format("woff");
  font-display: swap;
}

@font-face {
  font-family: "Space Text";
  src: url(/fonts/space-text-medium.woff) format("woff");
  font-display: swap;
}

@import url('https://fonts.googleapis.com/css2?family=Inter&display=swap');

html {
  font-family: "Inter", sans-serif;
}


@tailwind utilities;


This way, my base font was Inter, and I used font-heading and font-subheading in my classNames wherever I wanted something else. The setup was likely far from optimal, but it worked.


Enter @next/font

The first step I did was to install the package:

npm i @next/font
or
yarn add @next/font


Since I was using both local font files and Inter Google Fonts, I imported @next/font/google and @next/font/local in my root layout.tsx file in the ./app folder.

// ./app/layout.tsx

import { Inter } from "@next/font/google";
import localFont from "@next/font/local";


Setting up layout.tsx

Proceeding in the same file, I defined my three fonts. Note here, that the two local fonts have a variable defined, while my base font Inter does not. This is important for the next steps.

Side note: If you're wondering why the Inter font has subsets: ["latin"] defined, it's to only load a smaller part of the glyphs in the font when all of them are not used. The Next.js docs provide more information here.

// ./app/layout.tsx

import { Inter } from "@next/font/google";
import localFont from "@next/font/local";

const inter = Inter({
  subsets: ["latin"],
});

const madeDillan = localFont({
  src: "./assets/fonts/made-dillan.woff",
  variable: "--font-made-dillan",
});

const spaceText = localFont({
  src: "./assets/fonts/space-text-medium.woff",
  variable: "--font-space-text",
});


Moving on to the layout definition, here's where things get interesting. I have taken many classNames and combined them into one.

I already mentioned that my base font is Inter. That one I apply using inter.className. This makes all of the text on the page default to Inter.

Continuing, the other ones I add using madeDillan.variable and spaceText.variable. That way, I can use them in the next step.

// ./app/layout.tsx

export default function RootLayout({ children }: React.PropsWithChildren) {
  return (
    <html lang="en" className={`${inter.className} ${madeDillan.variable} ${spaceText.variable}`}>
      ...

Last step: tweaking the Tailwind config

You can now use the CSS variable defined from layout.tsx in your tailwind.config.js like this:

// tailwind.config.js

module.exports = {
  ...
  theme: {
    ...
    extend: {
      fontFamily: {
        heading: ["var(--font-made-dillan)"],
        subheading: ["var(--font-space-text)"],
      },
    }
  }
}

Now in my case, I could remove most things from my global.css.

/* ./style/global.css */

@tailwind base;
@tailwind components;
@tailwind utilities;

That's it! This setup lets me keep all of my components and their styling exactly as they were, while still benefiting from the features from @next/font.

Bonus: usage with the ./pages directory.


To be honest, I did not know this was possible, but I found out through Twitter. However, setup seems to be unclear and undocumented, so I figured out how to do the same setup in an app on Next 13 without the ./app directory.

First, I tried importing Inter from @next/font/google in _document.tsx. I thought that was resonable, since I have previously had <link> tags for loading fonts there. However, that cause this error.

Failed to compile Font loader error: Font loaders cannot be used within pages/_document.js. Location: pages/_document.js This error occurred during the build process and can only be dismissed by fixing the error.

Then I tried implementing something similar to what I did with above but in _app.tsx. It worked perfectly. Looks something like this:

// ./pages/_app.tsx

...

import { Inter } from "@next/font/google";
import localFont from "@next/font/local";

const inter = Inter({
  subsets: ["latin"],
});

const madeDillan = localFont({
  src: "./assets/fonts/made-dillan.woff",
  variable: "--font-made-dillan",
});

const spaceText = localFont({
  src: "./assets/fonts/space-text-medium.woff",
  variable: "--font-space-text",
});

export default function MyApp({ Component, pageProps }: AppProps) {
  return (
    <div className={`${inter.className} ${madeDillan.variable} ${spaceText.variable}`}>
      <Component {...pageProps} />
    </div>
  )
}



Hope this helps you try it out!
I'd love to hear your thoughts and feedback here.