---
title: "Font Optimization"
description: "The font optimization subsystem in Next.js automatically self-hosts Google Fonts and local font files at build time to eliminate external network requests, enhance privacy, and optimize performance..."
last_updated: "2026-10-12T06:38:56.845597+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/built-in-features/font-optimization"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [packages/font/src/google/loader.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/google/loader.ts)
- [packages/font/src/local/loader.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/local/loader.ts)
- [packages/next/font/local/index.js](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/font/local/index.js)
- [packages/next/src/build/webpack/loaders/next-font-loader/index.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/loaders/next-font-loader/index.ts)
- [packages/next/src/build/webpack/loaders/next-font-loader/postcss-next-font.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/loaders/next-font-loader/postcss-next-font.ts)
- [packages/font/src/local/get-fallback-metrics-from-font-file.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/local/get-fallback-metrics-from-font-file.ts)
- [packages/next/font/google/index.js](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/font/google/index.js)
- [packages/font/src/google/get-fallback-font-override-metrics.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/google/get-fallback-font-override-metrics.ts)
- [packages/next/src/build/webpack/config/blocks/css/index.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/config/blocks/css/index.ts)
- [packages/font/local/index.js](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/local/index.js)
- [packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts)
- [packages/next/font/google/target.css](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/font/google/target.css)
- [packages/next/src/build/webpack/loaders/css-loader/src/index.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/loaders/css-loader/src/index.ts)
- [packages/font/google/index.js](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/google/index.js)
- [packages/next/src/next-devtools/dev-overlay/font/font-styles.tsx](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/next-devtools/dev-overlay/font/font-styles.tsx)
- [packages/next/font/local/target.css](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/font/local/target.css)
- [packages/next/src/build/webpack/config/blocks/css/loaders/next-font.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/config/blocks/css/loaders/next-font.ts)
- [packages/next/src/server/font-utils.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/font-utils.ts)
- [packages/next/taskfile.js](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/taskfile.js)
- [packages/next/font/index.d.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/font/index.d.ts)
- [packages/font/src/local/pick-font-file-for-fallback-generation.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/local/pick-font-file-for-fallback-generation.ts)
- [packages/font/src/google/fetch-css-from-google-fonts.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/google/fetch-css-from-google-fonts.ts)
- [packages/font/google/target.css](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/google/target.css)
- [packages/next/src/build/webpack/loaders/next-metadata-route-loader.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/loaders/next-metadata-route-loader.ts)
- [packages/font/local/target.css](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/local/target.css)
- [packages/font/src/google/fetch-font-file.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/google/fetch-font-file.ts)
- [packages/font/src/local/index.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/local/index.ts)
- [packages/next/src/next-devtools/server/font/get-dev-overlay-font-middleware.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/next-devtools/server/font/get-dev-overlay-font-middleware.ts)
- [packages/next/src/shared/lib/turbopack/manifest-loader.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/shared/lib/turbopack/manifest-loader.ts)
- [packages/next/font/local/index.d.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/font/local/index.d.ts)
</details>

## Overview

The font optimization subsystem in Next.js automatically self-hosts Google Fonts and local font files at build time to eliminate external network requests, enhance privacy, and optimize performance. By integrating directly into the [build pipeline](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/build-system/build-pipeline) via custom loaders and PostCSS plugins, Next.js transforms declarative font function calls into optimized local `@font-face` declarations, injects scoped class names, computes layout-shift fallback metrics, and records route-specific font mappings for asset preloading.

Sources: [packages/font/src/google/loader.ts:28-162](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/google/loader.ts#L28-L162), [packages/font/src/local/loader.ts:15-112](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/local/loader.ts#L15-L112), [packages/next/src/build/webpack/loaders/next-font-loader/index.ts:9-168](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/loaders/next-font-loader/index.ts#L9-L168), [packages/next/src/build/webpack/loaders/next-font-loader/postcss-next-font.ts:21-188](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/loaders/next-font-loader/postcss-next-font.ts#L21-L188), [packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts:55-178](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts#L55-L178)

## Webpack Loader Pipeline Integration

### Virtual Target CSS and Transformation Flow

The SWC transform plugin (`next-transform-font`) converts font function declarations such as `Inter({ subset: ['latin'] })` into explicit CSS imports terminating in virtual target CSS files carrying a serialised JSON resource query. For instance, an `Inter` call becomes an import from `next/font/google/target.css?{"import":"Inter","subsets":["latin"]}`. This query string supplies the target font loader with the function name, call arguments, file path, and assigned variable name.

```mermaid
flowchart TD
    A["Font Function Call (e.g. Inter())"] -->|SWC Transform| B["CSS Import with JSON Query (?{...})"]
    B --> C["Client Style Loader (Client Only)"]
    C --> D["Next Font CSS Loader (css-loader)"]
    D --> E["next-font-loader"]
    E -->|Parse JSON Query & Invoke| F["Font Loader Module (Google / Local)"]
    F -->|Emit Font Binaries| G["Emitted Static Asset (.next/static/media)"]
    F -->|Return CSS & Fallback Data| E
    E -->|PostCSS Processing| H["PostCSS AST & Font Hash Meta"]
    H --> D
    D --> I["Optimized CSS Module Output"]
```

Sources: [packages/next/src/build/webpack/loaders/next-font-loader/index.ts:16-30](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/loaders/next-font-loader/index.ts#L16-L30), [packages/next/src/build/webpack/config/blocks/css/loaders/next-font.ts:13-69](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/config/blocks/css/loaders/next-font.ts#L13-L69)

> [!CAUTION]
> Using font optimization functions inside `pages/_document.js` triggers an explicit `NextFontError` and halts compilation. `next-font-loader` inspects the originating file path for `pages/_document.` and aborts before invoking font generation.

Sources: [packages/next/src/build/webpack/loaders/next-font-loader/index.ts:32-46](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/loaders/next-font-loader/index.ts#L32-L46)

### Loader Configuration Options and Pipeline Architecture

The next font loader pipeline pairs two primary [Webpack loaders](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/build-system/webpack-loaders) executed in sequence: `css-loader` configured with custom module rules, followed by `next-font-loader`. Client builds also prepend a client-side style loader. 

| Option Key | Target Loader | Value / Behavior | Purpose |
| :--- | :--- | :--- | :--- |
| `importLoaders` | `css-loader` | `1` | Forces `css-loader` to process imported stylesheets through subsequent loaders (i.e. `next-font-loader`). |
| `esModule` | `css-loader` | `false` | Enables CommonJS export mode for backwards compatibility. |
| `exportLocalsConvention` | `css-loader` | `'asIs'` | Prevents transformation of CSS module class names. |
| `exportOnlyLocals` | `css-loader` | `ctx.isServer` | Disables CSS emission on server-side renders, outputting local export mappings exclusively. |
| `mode` | `css-loader` | `'pure'` | Disallows global style exports to enable isolated CSS code-splitting without load-order concerns. |
| `getLocalIdent` | `css-loader` | Custom function returning `__${exportName}_${meta.fontFamilyHash}` | Generates scoped, unguessable identifiers using the hash computed from font CSS content. |
| `fontLoader` | `css-loader` | `true` | Signals `css-loader` to expose font exports directly from metadata. |

Sources: [packages/next/src/build/webpack/config/blocks/css/loaders/next-font.ts:13-69](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/config/blocks/css/loaders/next-font.ts#L13-L69)

During execution, `next-font-loader` parses the resource query, loads either `next/font/google` or `next/font/local` via `fontLoaderPath`, and supplies an `emitFontFile` helper. Font assets are interpolated into `static/media/[hash][selector-modifiers].[ext]`, where `-s` denotes size-adjust fallback font usage and `.p` indicates preloaded assets. Only client builds emit these files via `this.emitFile`, while both client and server receive the resulting asset URL paths with deployment IDs. Finally, the loader processes the generated CSS through PostCSS using `postcssNextFontPlugin` and passes the resulting PostCSS AST, fallback exports, and a 6-character SHA1 `fontFamilyHash` back to `css-loader` via the loader meta argument.

Sources: [packages/next/src/build/webpack/loaders/next-font-loader/index.ts:25-165](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/loaders/next-font-loader/index.ts#L25-L165), [packages/next/src/build/webpack/loaders/css-loader/src/index.ts:177-179](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/loaders/css-loader/src/index.ts#L177-L179)

## Google Fonts Ingestion and Downloading

### Font Loader Execution and Caching Architecture

The Google font loading pipeline orchestrates validation, URL generation, asset downloading, and CSS rewriting inside `nextFontGoogleFontLoader`. Function arguments are validated via `validateGoogleFontFunctionCall`, after which `getFontAxes` and `getGoogleFontsUrl` construct the Google Fonts stylesheet request URL. Memory caching is maintained using module-scoped `cssCache` and `fontCache` maps keyed by URL strings to prevent redundant network fetches across separate client and server compiler passes.

```typescript
const nextFontGoogleFontLoader: FontLoader = async ({
  functionName,
  data,
  emitFontFile,
  isDev,
  isServer,
}) => {
  const {
    fontFamily,
    weights,
    styles,
    display,
    preload,
    selectedVariableAxes,
    fallback,
    adjustFontFallback,
    variable,
    subsets,
  } = validateGoogleFontFunctionCall(functionName, data[0])

  // Validate and get the font axes required to generated the URL
  const fontAxes = getFontAxes(
    fontFamily,
    weights,
    styles,
    selectedVariableAxes
  )

  // Generate the Google Fonts URL from the font family, axes and display value
  const url = getGoogleFontsUrl(fontFamily, fontAxes, display)

  // Get precalculated fallback font metrics, used to generate the fallback font CSS
  const adjustFontFallbackMetrics: AdjustFontFallback | undefined =
    adjustFontFallback ? getFallbackFontOverrideMetrics(fontFamily) : undefined

  const result = {
    fallbackFonts: fallback,
    weight:
      weights.length === 1 && weights[0] !== 'variable'
        ? weights[0]
        : undefined,
    style: styles.length === 1 ? styles[0] : undefined,
    variable,
    adjustFontFallback: adjustFontFallbackMetrics,
  }

  try {
    /**
     * Hacky way to make sure the fetch is only done once.
     * Otherwise both the client and server compiler would fetch the CSS.
     * The reason we need to return the actual CSS from both the server and client is because a hash is generated based on the CSS content.
     */
    const hasCachedCSS = cssCache.has(url)
    // Fetch CSS from Google Fonts or get it from the cache
    let fontFaceDeclarations = hasCachedCSS
      ? cssCache.get(url)
      : await fetchCSSFromGoogleFonts(url, fontFamily, isDev).catch((err) => {
          console.error(err)
          return null
        })
    if (!hasCachedCSS) {
      cssCache.set(url, fontFaceDeclarations ?? null)
    } else {
      cssCache.delete(url)
    }
    if (fontFaceDeclarations == null) {
      nextFontError(`Failed to fetch \`${fontFamily}\` from Google Fonts.`)
    }

```
Sources: [packages/font/src/google/loader.ts:28-96](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/google/loader.ts#L28-L96)

> [!NOTE]
> `cssCache` entries are deleted upon their second access to clear transient cache references after both server and client compiler passes have retrieved the generated stylesheet content.

Sources: [packages/font/src/google/loader.ts:80-92](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/google/loader.ts#L80-L92)

### Asset Fetching, Retries, and Mocking

Stylesheet and font binary retrievals are handled through `fetchCSSFromGoogleFonts` and `fetchFontFile`, both of which wrap network requests in a 3-attempt `retry` helper function and rely on `fetchResource`. When the environment variable `NEXT_FONT_GOOGLE_MOCKED_RESPONSES` is populated, network calls are bypassed entirely to read test mocks from disk or memory.

```typescript
import { nextFontError } from '../next-font-error'
import { fetchResource } from './fetch-resource'
import { retry } from './retry'

/**
 * Fetches the CSS containing the @font-face declarations from Google Fonts.
 * The fetch has a user agent header with a modern browser to ensure we'll get .woff2 files.
 *
 * The env variable NEXT_FONT_GOOGLE_MOCKED_RESPONSES may be set containing a path to mocked data.
 * It's used to define mocked data to avoid hitting the Google Fonts API during tests.
 */
export async function fetchCSSFromGoogleFonts(
  url: string,
  fontFamily: string,
  isDev: boolean
): Promise<string> {
  if (process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES) {
    const mockFile = require(process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES)
    const mockedResponse = mockFile[url]
    if (!mockedResponse) {
      nextFontError('Missing mocked response for URL: ' + url)
    }
    return mockedResponse
  }

  const buffer = await retry(async () => {
    return fetchResource(
      url,
      isDev,
      `Failed to fetch font \`${fontFamily}\`: ${url}\n` +
        `Please check your network connection.`
    )
  }, 3)

  return buffer.toString('utf8')
}
```
Sources: [packages/font/src/google/fetch-css-from-google-fonts.ts:1-36](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/google/fetch-css-from-google-fonts.ts#L1-L36)

```typescript
import fs from 'node:fs'
import { retry } from './retry'
import { fetchResource } from './fetch-resource'

/**
 * Fetches a font file and returns its contents as a Buffer.
 * If NEXT_FONT_GOOGLE_MOCKED_RESPONSES is set, we handle mock data logic.
 */
export async function fetchFontFile(url: string, isDev: boolean) {
  if (process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES) {
    if (url.startsWith('/')) {
      return fs.readFileSync(url)
    }
    return Buffer.from(url)
  }

  return await retry(async () => {
    return fetchResource(
      url,
      isDev,
      `Failed to fetch font file from \`${url}\`.`
    )
  }, 3)
}
```
Sources: [packages/font/src/google/fetch-font-file.ts:1-24](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/google/fetch-font-file.ts#L1-L24)

> [!WARNING]
> In development mode (`isDev`), network failures during font asset downloading do not throw fatal compilation errors; instead, the loader catches the error and emits a fallback font CSS declaration using local metric overrides.

Sources: [packages/font/src/google/loader.ts:163-193](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/google/loader.ts#L163-L193)

### File Emission and CSS Rewriting

Once the `@font-face` CSS declarations are fetched, `findFontFilesInCss` parses the source to locate individual font assets. Each asset is downloaded, filtered for valid extensions matching `/\.(woff|woff2|eot|ttf|otf)$/`, and emitted via `emitFontFile` into `.next/static/media`. Finally, remote Google Fonts asset URLs inside the `@font-face` rules are replaced with self-hosted static asset paths.

```typescript
// CSS Variables may be set on a body tag, ignore them to keep the CSS module pure
fontFaceDeclarations = fontFaceDeclarations.split('body {', 1)[0]

// Find font files to download, provide the array of subsets we want to preload if preloading is enabled
const fontFiles = findFontFilesInCss(
  fontFaceDeclarations,
  preload ? subsets : undefined
)

// Download the font files extracted from the CSS
const downloadedFiles = await Promise.all(
  fontFiles.map(async ({ googleFontFileUrl, preloadFontFile }) => {
    const hasCachedFont = fontCache.has(googleFontFileUrl)
    // Download the font file or get it from cache
    const fontFileBuffer = hasCachedFont
      ? fontCache.get(googleFontFileUrl)
      : await fetchFontFile(googleFontFileUrl, isDev).catch((err) => {
          console.error(err)
          return null
        })
    if (!hasCachedFont) {
      fontCache.set(googleFontFileUrl, fontFileBuffer ?? null)
    } else {
      fontCache.delete(googleFontFileUrl)
    }
    if (fontFileBuffer == null) {
      nextFontError(`Failed to fetch \`${fontFamily}\` from Google Fonts.`)
    }

    const ext = /\.(woff|woff2|eot|ttf|otf)$/.exec(googleFontFileUrl)![1]
    // Emit font file to .next/static/media
    const selfHostedFileUrl = emitFontFile(
      fontFileBuffer,
      ext,
      preloadFontFile,
      !!adjustFontFallbackMetrics
    )

    return {
      googleFontFileUrl,
      selfHostedFileUrl,
    }
  })
)

/**
 * Replace the @font-face sources with the self-hosted files we just downloaded to .next/static/media
 *
 * E.g.
 * @font-face {
 *   font-family: 'Inter';
 *   src: url(https://fonts.gstatic.com/...) -> url(/_next/static/media/_.woff2)
 * }
 */
let updatedCssResponse = fontFaceDeclarations
for (const { googleFontFileUrl, selfHostedFileUrl } of downloadedFiles) {
  updatedCssResponse = updatedCssResponse.replace(
    new RegExp(escapeStringRegexp(googleFontFileUrl), 'g'),
    selfHostedFileUrl
  )
}

return {
  ...result,
  css: updatedCssResponse,
}
```
Sources: [packages/font/src/google/loader.ts:97-162](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/google/loader.ts#L97-L162)

## Google Font Fallback Metric Overrides

### Fallback Metric Calculation Pipeline

When `adjustFontFallback` is requested for a Google font, `nextFontGoogleFontLoader` invokes `getFallbackFontOverrideMetrics` to compute size adjustments and override percentages based on `capsize-font-metrics.json`. The calculation sequence flows through specific transformation functions in exact call order: `nextFontGoogleFontLoader` invokes `getFallbackFontOverrideMetrics`, which calls `calculateSizeAdjustValues`, which subsequently executes `formatName` to normalize the font family lookup key and finishes by executing `formatOverrideValue` to serialize the computed metrics.

```mermaid
sequenceDiagram
    participant loader as packages/font/src/google/loader.ts
    participant metrics as packages/font/src/google/get-fallback-font-override-metrics.ts
    participant utils as packages/next/src/server/font-utils.ts
    
    loader->>metrics: getFallbackFontOverrideMetrics(fontFamily)
    metrics->>utils: calculateSizeAdjustValues(fontFamily)
    utils->>utils: formatName(fontName)
    utils->>utils: formatOverrideValue(val)
```

1. `nextFontGoogleFontLoader` calls `getFallbackFontOverrideMetrics` with the font family name.
   Sources: [packages/font/src/google/loader.ts:60-61](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/google/loader.ts#L60-L61)
2. `getFallbackFontOverrideMetrics` delegates calculation to `calculateSizeAdjustValues`.
   Sources: [packages/font/src/google/get-fallback-font-override-metrics.ts:13-16](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/google/get-fallback-font-override-metrics.ts#L13-L16)
3. `calculateSizeAdjustValues` runs `formatName` to normalize the font family lookup key.
   Sources: [packages/next/src/server/font-utils.ts:19-20](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/font-utils.ts#L19-L20)
4. `calculateSizeAdjustValues` finishes by executing `formatOverrideValue` to serialize the computed metrics.
   Sources: [packages/next/src/server/font-utils.ts:33-42](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/font-utils.ts#L33-L42)

> [!NOTE]
> If metric calculation throws an error due to missing font data, `getFallbackFontOverrideMetrics` catches the exception and logs an error without halting compilation.
> Sources: [packages/font/src/google/get-fallback-font-override-metrics.ts:24-26](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/google/get-fallback-font-override-metrics.ts#L24-L26)

### Metric Transformation Functions and Output Types

The utility functions in `font-utils.ts` normalize font family strings and compute proportionate fallback metric values against standard system fonts. The resulting structure supplies property definitions for ascent, descent, line gap, and size adjustments.

```typescript
function formatName(str: string): string {
  return str
    .replace(/(?:^\w|[A-Z]|\b\w)/g, function (word, index) {
      return index === 0 ? word.toLowerCase() : word.toUpperCase()
    })
    .replace(/\s+/g, '')
}

function formatOverrideValue(val: number) {
  return Math.abs(val * 100).toFixed(2)
}
```
Sources: [packages/next/src/server/font-utils.ts:7-17](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/font-utils.ts#L7-L17)

```typescript
export type AdjustFontFallback = {
  fallbackFont: string
  ascentOverride?: string
  descentOverride?: string
  lineGapOverride?: string
  sizeAdjust?: string
}
```
Sources: [packages/next/font/index.d.ts:1-7](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/font/index.d.ts#L1-L7)

| Property Name | Type / Format | Description |
| :--- | :--- | :--- |
| `fallbackFont` | `string` | The default fallback family (`DEFAULT_SERIF_FONT` or `DEFAULT_SANS_SERIF_FONT`). |
| `ascentOverride` | `string` (percentage) | Normalized ascent metric scaled by size adjustment. |
| `descentOverride` | `string` (percentage) | Normalized descent metric scaled by size adjustment. |
| `lineGapOverride` | `string` (percentage) | Normalized line gap metric scaled by size adjustment. |
| `size` | `string` (percentage) | Computed `size-adjust` percentage comparing average character widths. |

Sources: [packages/next/src/server/font-utils.ts:33-42](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/font-utils.ts#L33-L42), [packages/next/font/index.d.ts:1-7](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/font/index.d.ts#L1-L7)

> [!IMPORTANT]
> `formatOverrideValue` multiplies values by 100 and fixes them to two decimal places to match CSS percentage descriptors required by `@font-face` metric override rules.
> Sources: [packages/next/src/server/font-utils.ts:15-17](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/font-utils.ts#L15-L17)

## Local Font Binary Metric Extraction

### Selecting Representative Local Font Files

When a local font configuration defines multiple font source files with varying weights and styles, `pickFontFileForFallbackGeneration` evaluates the array to select a single representative binary for automatic fallback metric extraction. The selection heuristic assumes that the bulk of page text uses normal weight (`400`) and normal style. It computes the numeric weight distance from normal using `getDistanceFromNormalWeight`, which parses standard CSS keywords or variable font weight ranges specified as two space-separated numbers. If two font files share the exact absolute distance from normal weight, the thinner font file is preferred.

```mermaid
flowchart TD
    Start["pickFontFileForFallbackGeneration(fontFiles)"] --> Iterate["Reduce fontFiles array"]
    Iterate --> CalcDist["Calculate weight distance from 400"]
    CalcDist --> CompareWeight{"Compare absolute weights"}
    CompareWeight -->|Current is closer| PickCurrent["Select current font file"]
    CompareWeight -->|Used is closer| KeepUsed["Keep previously selected file"]
    CompareWeight -->|Equal distance| CompareStyle{"Compare styles / thickness"}
    CompareStyle -->|Prefer normal / thinner| PickCurrent
    CompareStyle -->|Otherwise| KeepUsed
```
Sources: [packages/font/src/local/pick-font-file-for-fallback-generation.ts:10-57](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/local/pick-font-file-for-fallback-generation.ts#L10-L57), [packages/font/src/local/pick-font-file-for-fallback-generation.ts:68-103](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/local/pick-font-file-for-fallback-generation.ts#L68-L103)

> [!WARNING]
> If a variable font weight range is invalid and cannot be parsed into numbers, `nextFontError` halts execution with an explicit error message specifying expected formats.
> Sources: [packages/font/src/local/pick-font-file-for-fallback-generation.ts:31-36](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/local/pick-font-file-for-fallback-generation.ts#L31-L36)

### Extracting Size-Adjust Metrics via Fontkit

Once a representative font file binary is loaded into memory, `nextFontLocalFontLoader` passes the `fileBuffer` to `fontFromBuffer` from `fontkit`. If metadata parsing succeeds, `getFallbackMetricsFromFontFile` computes proportional fallback parameters by evaluating the average character width across a Latin reference string (`aaabcdeeeefghiijklmnnoopqrrssttuvwxyz      `) against system fallback baselines.

```typescript
const DEFAULT_SANS_SERIF_FONT = {
  name: 'Arial',
  azAvgWidth: 934.5116279069767,
  unitsPerEm: 2048,
}
const DEFAULT_SERIF_FONT = {
  name: 'Times New Roman',
  azAvgWidth: 854.3953488372093,
  unitsPerEm: 2048,
}
```
Sources: [packages/font/src/local/get-fallback-metrics-from-font-file.ts:6-15](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/local/get-fallback-metrics-from-font-file.ts#L6-L15)

| Fallback Font Constant | Target Name | Average Width (`azAvgWidth`) | Units Per Em |
| :--- | :--- | :--- | :--- |
| `DEFAULT_SANS_SERIF_FONT` | `Arial` | `934.5116279069767` | `2048` |
| `DEFAULT_SERIF_FONT` | `Times New Roman` | `854.3953488372093` | `2048` |

Sources: [packages/font/src/local/get-fallback-metrics-from-font-file.ts:6-15](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/local/get-fallback-metrics-from-font-file.ts#L6-L15)

> [!CAUTION]
> If the loaded font file lacks any code points required by the reference character string, `calcAverageWidth` aborts metric extraction and returns `undefined`, skipping automatic `size-adjust` generation for that font family.
> Sources: [packages/font/src/local/get-fallback-metrics-from-font-file.ts:35-41](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/font/src/local/get-fallback-metrics-from-font-file.ts#L35-L41)

## PostCSS Font Face Transformation

### Family Name Normalization and Hashing

The PostCSS plugin processes incoming `@font-face` rules by iterating through the AST nodes, extracting the `font-family` declaration, and removing quote characters using `normalizeFamily` before formatting them with single quotes. It derives an unguessable family name from the CSS content using `loaderUtils.getHashDigest` with SHA-1 truncated to 6 hex characters. If no `@font-face` rule with a `font-family` declaration is encountered, `postcssNextFontPlugin` throws an error.

```typescript
Once(root: any) {
  let fontFamily: string | undefined

  const normalizeFamily = (family: string) => {
    return family.replace(/['"]/g, '')
  }

  const formatFamily = (family: string) => {
    return `'${family}'`
  }

  // Hash font-family names
  for (const node of root.nodes) {
    if (node.type === 'atrule' && node.name === 'font-face') {
      const familyNode = node.nodes.find(
        (decl: Declaration) => decl.prop === 'font-family'
      )
      if (!familyNode) {
        continue
      }

      if (!fontFamily) {
        fontFamily = normalizeFamily(familyNode.value)
      }

      familyNode.value = formatFamily(fontFamily)
    }
  }

  if (!fontFamily) {
    throw new Error("Font loaders must return one or more @font-face's")
  }
```
Sources: [packages/next/src/build/webpack/loaders/next-font-loader/postcss-next-font.ts:38-69](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/loaders/next-font-loader/postcss-next-font.ts#L38-L69)

> [!WARNING]
> Font loaders must return one or more valid `@font-face` blocks containing a `font-family` property, or the transformation will fail with a `NextFontError`.
> Sources: [packages/next/src/build/webpack/loaders/next-font-loader/postcss-next-font.ts:67-69](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/loaders/next-font-loader/postcss-next-font.ts#L67-L69)

### Fallback Declaration and Rule Generation

When `adjustFontFallback` metrics are provided, the plugin generates an additional fallback `@font-face` block referencing a local system font and overrides. It also creates `.className` and `.variable` rules containing the complete font family stack, weight, and style descriptors unless they represent ranges.

```typescript
// Add fallback @font-face with the provided override values
let adjustFontFallbackFamily: string | undefined
if (adjustFontFallback) {
  adjustFontFallbackFamily = formatFamily(`${fontFamily} Fallback`)
  const fallbackFontFace = postcss.atRule({ name: 'font-face' })
  const {
    fallbackFont,
    ascentOverride,
    descentOverride,
    lineGapOverride,
    sizeAdjust,
  } = adjustFontFallback
  fallbackFontFace.nodes = [
    new postcss.Declaration({
      prop: 'font-family',
      value: adjustFontFallbackFamily,
    }),
    new postcss.Declaration({
      prop: 'src',
      value: `local("${fallbackFont}")`,
    }),
    ...(ascentOverride
      ? [
          new postcss.Declaration({
            prop: 'ascent-override',
            value: ascentOverride,
          }),
        ]
      : []),
    ...(descentOverride
      ? [
          new postcss.Declaration({
            prop: 'descent-override',
            value: descentOverride,
          }),
        ]
      : []),
    ...(lineGapOverride
      ? [
          new postcss.Declaration({
            prop: 'line-gap-override',
            value: lineGapOverride,
          }),
        ]
      : []),
    ...(sizeAdjust
      ? [
          new postcss.Declaration({
            prop: 'size-adjust',
            value: sizeAdjust,
          }),
        ]
      : []),
  ]
  root.nodes.push(fallbackFontFace)
}
```
Sources: [packages/next/src/build/webpack/loaders/next-font-loader/postcss-next-font.ts:71-126](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/loaders/next-font-loader/postcss-next-font.ts#L71-L126)

| At-Rule / Rule Property | Target AST Property | Source Configuration Field |
| :--- | :--- | :--- |
| `font-family` | `adjustFontFallbackFamily` | `adjustFontFallback` |
| `src` | `local("${fallbackFont}")` | `adjustFontFallback.fallbackFont` |
| `ascent-override` | `ascentOverride` | `adjustFontFallback.ascentOverride` |
| `descent-override` | `descentOverride` | `adjustFontFallback.descentOverride` |
| `line-gap-override` | `lineGapOverride` | `adjustFontFallback.lineGapOverride` |
| `size-adjust` | `sizeAdjust` | `adjustFontFallback.sizeAdjust` |

Sources: [packages/next/src/build/webpack/loaders/next-font-loader/postcss-next-font.ts:83-124](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/loaders/next-font-loader/postcss-next-font.ts#L83-L124)

> [!TIP]
> Variable weight or style values containing spaces are identified as ranges using `isRange` and are omitted from direct class property declarations to prevent invalid CSS output.
> Sources: [packages/next/src/build/webpack/loaders/next-font-loader/postcss-next-font.ts:128-162](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/loaders/next-font-loader/postcss-next-font.ts#L128-L162)

## Manifest Generation and Route Preloading

### NextFontManifestPlugin Architecture

The `NextFontManifestPlugin` collects all font files emitted by `next-font-loader` during compilation and constructs a structured JSON and Edge-compatible manifest file. This manifest is subsequently consumed by Next.js server render functions in `pages/` and `app/` to inject precise preload or preconnect links for fonts utilized on the active route.

```typescript
/**
 * The NextFontManifestPlugin collects all font files emitted by next-font-loader and creates a manifest file.
 * The manifest file is used in the Next.js render functions (_document.tsx for pages/ and app-render for app/) to add preload tags for the font files.
 * We only want to att preload fonts that are used by the current route.
 *
 * For pages/ the plugin finds the fonts imported in the entrypoint chunks and creates a map:
 * { [route]: fontFile[] }
 * When rendering the app in _document.tsx, it gets the font files to preload: manifest.pages[currentRouteBeingRendered].
 *
 * For app/, the manifest is a bit different.
 * Instead of creating a map of route to font files, it creates a map of the webpack module request to font files.
 * { [webpackModuleRequest]: fontFile[]]}
 * When creating the component tree in app-render it looks for font files to preload: manifest.app[moduleBeingRendered]
 */
```
Sources: [packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts:41-54](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts#L41-L54)

```mermaid
flowchart TD
    A["Compilation Make / Process Assets"] --> B{"appDir Defined?"}
    B -- Yes --> C["Traverse app/ Modules for next-font-loader"]
    C --> D["Extract Module Assets & Check Size Adjust"]
    D --> E["Filter Preloaded Font Files (.p.extension)"]
    E --> F["Populate nextFontManifest.app"]
    B -- No --> G["Iterate Compilation Entrypoints for pages/"]
    F --> G
    G --> H["Extract Auxiliary Chunk Font Files"]
    H --> I["Populate nextFontManifest.pages"]
    I --> J["Emit server/next-font-manifest.json & .js"]
```
Sources: [packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts:63-173](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts#L63-L173)

| Manifest Field / Helper | Type / Return | Purpose |
| :--- | :--- | :--- |
| `pages` | `{[path: string]: string[]}` | Maps route paths in `pages/` to arrays of preloaded font file names. |
| `app` | `{[entry: string]: string[]}` | Maps webpack module/chunk entries in `app/` to arrays of preloaded font files. |
| `appUsingSizeAdjust` | `boolean` | Tracks whether any app font uses size-adjust fallback metrics (`-s` in file name). |
| `pagesUsingSizeAdjust` | `boolean` | Tracks whether any pages font uses size-adjust fallback metrics. |
| `getPreloadedFontFiles` | `string[]` | Filters font file arrays for extensions matching `/\.p\.(woff|woff2|eot|ttf|otf)$/`. |
| `getPageIsUsingSizeAdjust` | `boolean` | Returns true if any font file string includes `-s`. |

Sources: [packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts:7-39](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts#L7-L39)

> [!NOTE]
> When a font entry is present in the manifest but contains an empty array of preloaded files, Next.js injects a `preconnect` link tag instead of a `preload` tag for that font family.
> Sources: [packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts:107-112](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts#L107-L112)

### Dev Overlay Font Serving

During local development, the Next.js dev overlay manages its own isolated typography requirements via `FontStyles` and `getDevOverlayFontMiddleware`. The middleware intercepts requests matching `/__nextjs_font/`, checks file existence, and serves hardcoded fallback font binaries with immutable cache headers.

```typescript
const FONT_PREFIX = '/__nextjs_font/'

const VALID_FONTS = [
  'geist-latin-ext.woff2',
  'geist-mono-latin-ext.woff2',
  'geist-latin.woff2',
  'geist-mono-latin.woff2',
]

const FONT_HEADERS = {
  'Content-Type': 'font/woff2',
  'Cache-Control': 'public, max-age=31536000, immutable',
} as const

```
Sources: [packages/next/src/next-devtools/server/font/get-dev-overlay-font-middleware.ts:8-21](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/next-devtools/server/font/get-dev-overlay-font-middleware.ts#L8-L21)

| Valid Dev Overlay Font | Content-Type | Cache-Control Header |
| :--- | :--- | :--- |
| `geist-latin-ext.woff2` | `font/woff2` | `public, max-age=31536000, immutable` |
| `geist-mono-latin-ext.woff2` | `font/woff2` | `public, max-age=31536000, immutable` |
| `geist-latin.woff2` | `font/woff2` | `public, max-age=31536000, immutable` |
| `geist-mono-latin.woff2` | `font/woff2` | `public, max-age=31536000, immutable` |

Sources: [packages/next/src/next-devtools/server/font/get-dev-overlay-font-middleware.ts:10-20](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/next-devtools/server/font/get-dev-overlay-font-middleware.ts#L10-L20)

> [!WARNING]
> Requests to the dev overlay font middleware whose pathnames do not start with `/__nextjs_font/` or do not match the exact entries in `VALID_FONTS` immediately invoke `next()` or return a `404 Not Found` response.
> Sources: [packages/next/src/next-devtools/server/font/get-dev-overlay-font-middleware.ts:31-38](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/next-devtools/server/font/get-dev-overlay-font-middleware.ts#L31-L38)

## Related

- [Webpack Configuration](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/build-system/webpack-configuration)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/llms.txt) for all pages in this wiki.
