Load and optimize fonts in Next.js

LinkIcon1. Install Fonts

Download open source fonts from

LinkIcon2. Optimize Font

Usually the fonts downloaded comes in the format .ttf. They are high quality but files are larger. Not intended for web use.

The recommeded is .woof2 as the primary format and .woof as fallback.

To optimize them need to convert .ttf to .woff2 format and for that we will use:

LinkIcon3. Import Font

TypescriptIconsrc/fonts/gluten.ts
import localFont from "next/font/local"
 
export const gluten = localFont({
	src: [
		{ path: "./Gluten-Thin.woff2", weight: "100", style: "normal" },
		{ path: "./Gluten-ExtraLight.woff2", weight: "200", style: "normal" },
		{ path: "./Gluten-Light.woff2", weight: "300", style: "normal" },
		{ path: "./Gluten-Regular.woff2", weight: "400", style: "normal" },
		{ path: "./Gluten-Medium.woff2", weight: "500", style: "normal" },
		{ path: "./Gluten-SemiBold.woff2", weight: "600", style: "normal" },
		{ path: "./Gluten-Bold.woff2", weight: "700", style: "normal" },
		{ path: "./Gluten-ExtraBold.woff2", weight: "800", style: "normal" },
		{ path: "./Gluten-Black.woff2", weight: "900", style: "normal" }
	],
	variable: "--font-gluten", // used with CSS variables
	display: "swap"
})

If want to import manually in the header element:

HTMLIcon
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Gluten&display=swap&subset=latin" as="font" type="font/woff2" crossorigin>
 
<!-- or if importing locally -->
<link rel="preload" href="/fonts/roboto-regular.woff2" as="font" type="font/woff2" crossorigin>

Things to consider:

  • Dont forget to add subset to reduce file size
  • Add preconnect
  • Host font locally
  • Use 2–3 weights max (e.g., 400, 500, 700).
  • Use "variable font" (a single font that contains multiple weights) instead of "static font" (multiple files of different font weights)

LinkIcon4. Apply Font

LinkIconApply directly

  • Quickly and simpler
  • Sets by default font to the element and its children unless it's overrided.
  • Use case: use for certain components
<html lang="en" className={`... ${gluten.className}`}>

LinkIconApply using CSS variables with TailwindCSS

  • Requires more setup
  • Font as a tailwind variable

Assuming Tailwindcss v4 installed

CssIconsrc/app/global.css
@theme inline {
  ...
  --font-primary: var(--font-gluten);
  ...
}

Then, also add

ReactIconsrc/app/layout.tsx
<html lang="en">
  <body className={cn("font-primary", gluten.variable)}>
    {children}
  </body>
</html>

Now you can use font-primary in any component or html element.

Under the hood, adding gluten.variable will:

  • add in the body element: --font-gluten: "gluten"
  • generate css code with @font-face{} for every font-weight.
@font-face {
  font-family: 'gluten';
  src: url('@vercel/turbopack-next/internal/font/local/font?{%22path%22:%22./Gluten-Thin.woff2%22,%22preload%22:true,%22has_size_adjust%22:true}') format('woff2');
  font-display: swap;
  font-weight: 100;
  font-style: normal;
}
 
...
 
@font-face {
  font-family: 'gluten Fallback';
  src: local("Arial");
  ascent-override: 55.81%;
  descent-override: 20.55%;
  line-gap-override: 0.00%;
  size-adjust: 115.56%;
}
 
.className { font-family: 'gluten', 'gluten Fallback'; }
.variable { --font-gluten: 'gluten', 'gluten Fallback'; }