next/font
Self-hosts Google or local fonts at build time and generates a matched fallback to prevent layout shift.
import { Inter } from 'next/font/google'
import localFont from 'next/font/local'
Inter(options?: {
weight?: string | string[]
style?: string | string[]
subsets?: string[]
axes?: string[]
display?: 'auto' | 'block' | 'swap' | 'fallback' | 'optional'
preload?: boolean
fallback?: string[]
adjustFontFallback?: boolean
variable?: string
}): NextFont
localFont(options: {
src: string | Array<{ path: string; weight?: string; style?: string }>
weight?: string | string[]
style?: string | string[]
display?: 'auto' | 'block' | 'swap' | 'fallback' | 'optional'
preload?: boolean
fallback?: string[]
adjustFontFallback?: 'Arial' | 'Times New Roman' | false
variable?: string
declarations?: Array<{ prop: string; value: string }>
}): NextFont- Added in Next.js v13.0.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$src | string | Array<{ path: string; weight?: string; style?: string }> | next/font/local only, and required there. Path to the font file relative to the file calling the loader, or an array of files describing each weight and style of one family. |
$weightoptional | string | string[] | A single weight ('400'), a range for a variable font ('100 900'), or an array of weights for a non-variable Google font. Required when the font is not variable. |
$styleoptional | string | string[]= 'normal' | 'normal' or 'italic' for Google fonts; any standard font-style value for local fonts. An array is allowed for non-variable Google fonts. |
$subsetsoptional | string[] | next/font/google only. Character subsets to preload, for example ['latin']. Each one gets a preload link in the head while preload is true. |
$axesoptional | string[] | next/font/google only. Extra variable-font axes beyond weight, such as ['slnt']. Each added axis increases the file size. |
$displayoptional | 'auto' | 'block' | 'swap' | 'fallback' | 'optional'= 'swap' | The CSS font-display descriptor written into the generated @font-face rule. |
$preloadoptional | boolean= true | Whether to emit a preload link for the font files on the routes that use it. |
$fallbackoptional | string[] | Fallback families appended to font-family if the font cannot load, for example ['system-ui', 'arial']. |
$adjustFontFallbackoptional | boolean | 'Arial' | 'Times New Roman'= true for google, 'Arial' for local | Generates a metric-matched fallback face to cut layout shift. Boolean for next/font/google; for next/font/local it is 'Arial', 'Times New Roman' or false. |
$variableoptional | string | Name of the CSS custom property to declare, for example '--font-inter', for use through the returned variable class. |
$declarationsoptional | Array<{ prop: string; value: string }> | next/font/local only. Extra @font-face descriptors, for example [{ prop: 'ascent-override', value: '90%' }]. |
Return value
{ className: string; style: { fontFamily: string; fontWeight?: number; fontStyle?: string }; variable: string }A read-only object with a className to apply the font, a style object exposing fontFamily and the generated fallbacks, and a variable class that declares the CSS custom property named by the variable option.
Errors
A non-variable Google font called without weight fails the build, arguments that are not literal values fail to compile with "Font loader values must be explicitly written literals", and a Google font with preload left on but no subsets logs a warning.
Changelog
- PHP v15.0.0The external @next/font package is no longer supported, and font-family hashing was removed.
- PHP v13.2.0@next/font renamed to next/font and bundled with Next.js - no separate install.
- PHP v13.0.0@next/font added.
Calling a font loader downloads the CSS and font files at build time and serves them from your own origin, so the browser never contacts Google. Alongside the @font-face rule, Next.js generates a metric-matched fallback face, which is what actually removes the layout shift when the real font swaps in.
Applying the result
Use className for the common case, style when you need the family name inline, and variable when the typography lives in a stylesheet or in Tailwind's theme. Variable fonts need no weight; anything else does.
The rule that catches everyone
Loader calls must sit at module scope with literal arguments. You cannot build the options object in a variable, call the loader inside a component, or pick the font name dynamically — the compiler reads these values statically. Each call also produces a separate hosted instance, so declare fonts once in a shared fonts.ts and import the object rather than re-calling Inter() per file.
Preloading is scoped to where the loader is called: a root layout preloads on every route, a page only on that route. Font names with several words use an underscore, so Roboto Mono is imported as Roboto_Mono.
Example
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,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
)
}When to use it
- Set a site-wide typeface in the root layout with no external network request.
- Expose two families as CSS variables and wire them into Tailwind's --font-sans and --font-mono.
- Ship a licensed woff2 file from the repo with one file per weight.
- Keep every definition in one fonts.ts so a family is only hosted once.
Frequently asked questions
Why does the build fail with "Font loader values must be explicitly written literals"?
The loader was called with a variable, a computed value or a spread options object. Every argument has to be written literally at the call site, at module scope, because the values are read statically at build time.
When do I have to pass weight?
Whenever the font is not a variable font. Variable Google fonts such as Inter cover the whole range without it, while a family like Roboto needs weight: '400' or an array of weights.
Why do I get a warning about subsets?
A Google font is preloaded by default, and Next.js cannot decide which character subset to preload for you. Add subsets: ['latin'] (or whichever you need), or set preload: false.
More examples
A local family with one file per weight
Each entry becomes its own @font-face rule under a single family name, so font-weight: 700 in CSS picks the bold file with no further wiring. The paths resolve relative to this file, which is why the woff2 files sit next to app/layout.tsx here.
import localFont from 'next/font/local'
const roboto = localFont({
src: [
{ path: './Roboto-Regular.woff2', weight: '400', style: 'normal' },
{ path: './Roboto-Italic.woff2', weight: '400', style: 'italic' },
{ path: './Roboto-Bold.woff2', weight: '700', style: 'normal' },
{ path: './Roboto-BoldItalic.woff2', weight: '700', style: 'italic' },
],
display: 'swap',
variable: '--font-roboto',
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={roboto.variable}>
<body>{children}</body>
</html>
)
}One definitions file, imported everywhere
Source_Sans_3 is not variable, so omitting weight here would fail the build - Inter, which is variable, needs none. Exporting the objects instead of re-calling the loaders keeps one hosted instance per family; calling Inter() in three files would host it three times.
// styles/fonts.ts
import { Inter, Source_Sans_3 } from 'next/font/google'
import localFont from 'next/font/local'
export const inter = Inter({ subsets: ['latin'], display: 'swap' })
export const sourceSans400 = Source_Sans_3({
weight: '400',
subsets: ['latin'],
})
export const greatVibes = localFont({ src: './GreatVibes-Regular.ttf' })
// app/page.tsx
// import { inter, greatVibes } from '@/styles/fonts'
//
// export default function Page() {
// return (
// <div>
// <h1 className={greatVibes.className}>A display heading</h1>
// <p className={inter.className}>Body copy</p>
// </div>
// )
// }Feeding the variable into Tailwind
inter.variable is a class name, not the custom property itself - applying it to <html> is what declares --font-inter for everything inside. Once the theme maps it, the font-sans and font-mono utilities resolve to the self-hosted faces.
import { Inter, Roboto_Mono } from 'next/font/google'
const inter = Inter({ subsets: ['latin'], variable: '--font-inter' })
const robotoMono = Roboto_Mono({
subsets: ['latin'],
variable: '--font-roboto-mono',
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html
lang="en"
className={`${inter.variable} ${robotoMono.variable} antialiased`}
>
<body>{children}</body>
</html>
)
}
// global.css
// @import 'tailwindcss';
//
// @theme inline {
// --font-sans: var(--font-inter);
// --font-mono: var(--font-roboto-mono);
// }
Discussion