perf hacks
AssetsFonts

Font Optimization

Optimize web fonts for faster loading and zero layout shift

Web fonts can significantly impact performance, causing layout shifts (CLS) and delaying text rendering. Next.js provides built-in font optimization through next/font.

The Problem with Web Fonts

Without optimization, fonts cause:

  • FOIT (Flash of Invisible Text): Text hidden until font loads
  • FOUT (Flash of Unstyled Text): Fallback font replaced by web font
  • CLS: Layout shifts when fonts swap
  • Slow LCP: If text is the LCP element

Using next/font

Google Fonts

app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-inter',
});

const robotoMono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-roboto-mono',
});

export default function RootLayout({ children }) {
  return (
    <html className={`${inter.variable} ${robotoMono.variable}`}>
      <body className={inter.className}>{children}</body>
    </html>
  );
}

Local Fonts

import localFont from 'next/font/local';

const myFont = localFont({
  src: [
    {
      path: './fonts/MyFont-Regular.woff2',
      weight: '400',
      style: 'normal',
    },
    {
      path: './fonts/MyFont-Bold.woff2',
      weight: '700',
      style: 'normal',
    },
  ],
  display: 'swap',
  variable: '--font-my-font',
});

Variable Fonts

import { Inter } from 'next/font/google';

// Variable font - single file, all weights
const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  // Variable fonts support weight ranges
  weight: 'variable', // or specific: ['400', '500', '700']
});

Font Display Strategies

const font = Inter({
  subsets: ['latin'],
  display: 'swap', // Recommended for most cases
});
ValueBehaviorUse Case
swapShow fallback, then swapMost content
optionalUse fallback if slowNon-critical text
blockBrief invisible, then fontBrand text
fallbackBrief invisible, then fallbackCompromise
autoBrowser decidesAvoid

Preventing Layout Shift

adjustFontFallback

const inter = Inter({
  subsets: ['latin'],
  adjustFontFallback: true, // Adjusts fallback metrics
});

This generates CSS that adjusts the fallback font to match the web font's metrics:

/* Generated automatically */
@font-face {
  font-family: '__Inter_Fallback';
  src: local('Arial');
  ascent-override: 90.49%;
  descent-override: 22.56%;
  line-gap-override: 0%;
  size-adjust: 107.06%;
}

Custom Fallback Metrics

const myFont = localFont({
  src: './fonts/MyFont.woff2',
  fallback: ['system-ui', 'arial'],
  adjustFontFallback: 'Arial', // Base metrics on Arial
});

Subsetting

Only include characters you need:

// Latin only (smaller file)
const inter = Inter({
  subsets: ['latin'],
});

// Multiple subsets if needed
const notoSans = Noto_Sans({
  subsets: ['latin', 'cyrillic', 'greek'],
});
SubsetSize Impact
latinBase size
latin-ext+5-10%
cyrillic+10-20%
All subsets2-3x base

Preloading Critical Fonts

// next/font automatically preloads
// For manual preloading:
export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        <link
          rel="preload"
          href="/fonts/MyFont.woff2"
          as="font"
          type="font/woff2"
          crossOrigin="anonymous"
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

CSS Variables for Flexibility

// Define fonts with CSS variables
const inter = Inter({
  subsets: ['latin'],
  variable: '--font-sans',
});

const mono = Roboto_Mono({
  subsets: ['latin'],
  variable: '--font-mono',
});

// In layout
<html className={`${inter.variable} ${mono.variable}`}>

// In CSS/Tailwind
// tailwind.config.js
module.exports = {
  theme: {
    fontFamily: {
      sans: ['var(--font-sans)', 'system-ui'],
      mono: ['var(--font-mono)', 'monospace'],
    },
  },
};

Performance Comparison

ApproachWOFF2 SizeCLSFCP Impact
No optimization100KB+0.1-0.3+500ms
next/font Google20-40KB~0+50ms
next/font localVariable~0+20ms
System fonts only0KB00ms

Font Loading Strategies

Critical vs Non-Critical

// Critical fonts - load immediately
const headingFont = Inter({
  subsets: ['latin'],
  display: 'swap',
  preload: true,
});

// Non-critical - can load later
const codeFont = Roboto_Mono({
  subsets: ['latin'],
  display: 'optional',
  preload: false,
});

Reduce Font Weights

// ❌ Loading all weights
const inter = Inter({
  subsets: ['latin'],
  weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
});

// ✅ Only what you need
const inter = Inter({
  subsets: ['latin'],
  weight: ['400', '500', '700'],
});

System Font Stack

For maximum performance, consider system fonts:

font-family:
  -apple-system,
  BlinkMacSystemFont,
  'Segoe UI',
  Roboto,
  Oxygen,
  Ubuntu,
  Cantarell,
  'Open Sans',
  'Helvetica Neue',
  sans-serif;

No download required, instant rendering, zero CLS.

Debugging Font Issues

Check Font Loading

// In browser console
document.fonts.ready.then(() => {
  console.log('Fonts loaded:', document.fonts.size);
  document.fonts.forEach(font => {
    console.log(font.family, font.weight, font.status);
  });
});

Font Loading Timeline

// Monitor font loading performance
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.initiatorType === 'css' && entry.name.includes('font')) {
      console.log('Font loaded:', entry.name, entry.duration);
    }
  }
});

observer.observe({ entryTypes: ['resource'] });

Checklist

Setup

Use next/font for all fonts Automatic optimization, self-hosting, and zero CLS.

Enable adjustFontFallback Matches fallback metrics to prevent layout shift.

Set display: 'swap' Show fallback immediately while font loads.

Use CSS variables Flexible theming with --font-sans and --font-mono.

Optimization

Subset to needed characters Only include Latin if you don't need Cyrillic.

Limit font weights Include only weights you actually use.

Use variable fonts when possible Single file for all weights and styles.

Preload critical fonts next/font does this automatically.

Performance

Check CLS in DevTools Layout Shift Regions highlight font-related shifts.

Verify font-display behavior Check Network tab for font load timing.

Measure font load time Use Performance panel to see font download duration.

Consider system fonts for speed Zero download, instant rendering.

On this page