Technology · Next.js

Next.js Font Optimization

Load web fonts efficiently with next/font for performance and UX.

TL;DR
  1. 01Use next/font to load Google Fonts automatically.
  2. 02Font files are hosted locally for faster loading.
  3. 03Subset and weight fonts to reduce bundle size.

Google Fonts

  • Import and use Google Fonts with next/font.
    import { Inter } from "next/font/google";
    
    const inter = Inter({ subsets: ["latin"] });
    
    export default function RootLayout({ children }) {
      return (
        <html className={inter.className}>
          <body>{children}</body>
        </html>
      );
    }
  • Fonts are downloaded at build time and hosted locally.
  • No additional network requests for fonts during page loads.
  • Automatic font fallback to prevent layout shifts.

Multiple Fonts

  • Use multiple Google Fonts in the same project.
    import { Inter, Playfair_Display } from "next/font/google";
    
    const inter = Inter({ subsets: ["latin"] });
    const playfair = Playfair_Display({
      weight: ["400", "700"],
      subsets: ["latin"]
    });
    
    export default function Page() {
      return (
        <div>
          <h1 className={playfair.className}>Title</h1>
          <p className={inter.className}>Body text</p>
        </div>
      );
    }
  • Apply different fonts to different elements.
  • Reduce bundle size by specifying only needed weights.
  • Use CSS variables to apply fonts globally via Tailwind or CSS.
    const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
    
    export default function RootLayout({ children }) {
      return <html className={inter.variable}>{children}</html>;
    }
    // In CSS: font-family: var(--font-inter);
  • Export fonts from a shared module to avoid duplicate declarations.
    // lib/fonts.ts
    import { Inter, Roboto_Mono } from "next/font/google";
    
    export const inter = Inter({ subsets: ["latin"] });
    export const mono = Roboto_Mono({ subsets: ["latin"] });

Font Weights and Subsets

  • Specify weights to load only what you need.
    const inter = Inter({
      weight: ["400", "500", "700"],
      subsets: ["latin"]
    });
  • Use subsets to reduce font file size.
    const playfair = Playfair_Display({
      subsets: ["latin"],  // Only Latin characters
      display: "swap"      // Show fallback while loading
    });
  • Display property controls fallback behavior.
    const font = Inter({
      display: "swap",     // Show fallback immediately
      // "auto" (default), "block", "fallback", "optional"
    });
  • Use adjustFontFallback to reduce cumulative layout shift further.
    const inter = Inter({
      subsets: ["latin"],
      adjustFontFallback: true // adjusts metrics of fallback font
    });
  • Load a variable font instead of multiple weights for best performance.
    const inter = Inter({
      subsets: ["latin"]
      // Inter is a variable font — no weight array needed
    });

Custom Fonts

  • Load custom fonts from local files.
    import localFont from "next/font/local";
    
    const customFont = localFont({
      src: [
        {
          path: "../fonts/custom.woff2",
          weight: "400",
          style: "normal"
        },
        {
          path: "../fonts/custom-bold.woff2",
          weight: "700",
          style: "normal"
        }
      ]
    });
  • Place font files in the public folder.
    public/
      fonts/
        custom.woff2
        custom-bold.woff2
  • Use the custom font in components.
  • Apply a custom font via className on a wrapper element.
    export default function RootLayout({ children }) {
      return (
        <html className={customFont.className}>
          <body>{children}</body>
        </html>
      );
    }
  • Use a CSS variable with a local font to integrate with Tailwind.
    const customFont = localFont({
      src: "../fonts/custom.woff2",
      variable: "--font-custom"
    });
    // tailwind.config: fontFamily: { brand: ["var(--font-custom)"] }

Performance Best Practices

  • Use font display swap to prevent layout shift.
    const inter = Inter({
      display: "swap",  // Shows fallback font immediately
      subsets: ["latin"]
    });
  • Load fonts early in the layout for best results.
    // app/layout.tsx (root layout)
    import { Inter } from "next/font/google";
    
    const inter = Inter();
    
    export default function RootLayout({ children }) {
      return <html className={inter.className}>{children}</html>;
    }
  • Subset fonts to languages you support.
    const inter = Inter({
      subsets: ["latin", "latin-ext"]  // Add extended Latin
    });
  • Measure font impact on Core Web Vitals using web-vitals.
    import { getCLS } from "web-vitals";
    getCLS(console.log); // CLS score should be near 0 with font optimization
  • Use preload: false to skip preloading a font that is not above the fold.
    const mono = Roboto_Mono({
      subsets: ["latin"],
      preload: false // Only loaded when used, not preloaded
    });

Tip: Use next/font to load Google Fonts — it handles optimization automatically and improves Core Web Vitals.

Warning: Don't load too many font weights or families — each adds overhead. Stick to 2–3 fonts with a few weights each.

Next.js File StructureNext.js Getting Started