How to optimize custom fonts with next/font
Next.js 13 introduced the next/font package which allows for optimized self-hosting for any font file. This means you can serve your own fonts or fonts from Google Fonts, use them with CSS variables and achieve zero layout shift.
You can now migrate to the built-in next/font
by running
npx @next/codemod@latest built-in-next-font .
π
The @next/font
package will be removed in Next.js 14.
Of the many exciting features announced in Next.js 13, the newΒ @next/font
Β module is a humble yet powerful addition that significantly simplifies font optimization.
next/font
automatically optimizes your fonts and removes external network requests for improved privacy and performance. In addition, it generates fallback fonts with best-practices values for font-display
, size-adjust
(and more) to achieve zero layout shift as your app loads.
Zero layout shift is important as it ensures that your app is visually stable, which is a web vitals metric (CLS) relevant to how users perceive performance on the web. In short, web pages with less shifting and interference in the layout as the page loads are perceived as more performant.
How to install @next/font
In Next.js 13.2 and later, next/font
is built-in to Next.js
and no longer needs to be installed.
Add the package as a dependency to begin:
yarn add @next/font
# or
npm i @next/font
# or
pnpm i @next/font
Goal: self-host multiple font families and use them globally with CSS variables
Defining CSS variables on :root
or html
allows you to use that variable anywhere in your app. This is particularly useful when defining app-wide styling like fonts and colours.
Take this contrived example:
<html>
<head>
<style>
:root {
--font-base: 'myFont';
}
body {
font-family: var(--font-base);
}
</style>
</head>
<body>
<h1>G'day cobba π¦</h1>
</body>
</html>
With this approach, you can easily change your app's font by only changing one line of CSS. Additionally, your CSS is now far more robust as there's only one source of truth that defines exactly what --font-base
actually is.
When using localFont
from next/font/local
you can load multiple font-families or font weights by passing in an array of items to src
.
const myFont = localFont({
src: [
{
path: '../assets/fonts/my-font-regular.woff2',
weight: '300',
},
{
path: '../assets/fonts/my-font-bold.woff2',
weight: '600',
},
],
})
However, the approach in the Next.js docs doesn't cater to cases where you want to use the CSS variable on body
. In addition, using next/font
in _document.tsx
where the body
tag is rendered is also not supported.
error - Font loader error:
Font loaders cannot be used within pages/_document.js.
Working around this constraint is luckily straightforward by using <style jsx global>
in _app
, so let's put it all together:
import 'src/styles/globals.scss'
import localFont from 'next/font/local'
import type { AppProps } from 'next/app'
const myFont = localFont({
src: [
{
path: '../assets/fonts/my-font-regular.woff2',
weight: '300',
},
{
path: '../assets/fonts/my-font-bold.woff2',
weight: '600',
},
],
fallback: ['Helvetica', 'ui-sans-serif'],
})
function App({ Component, pageProps }: AppProps): JSX.Element {
return (
<>
<style jsx global>{`
:root {
--font-base: ${myFont.style.fontFamily};
}
`}</style>
<Component {...pageProps} />
</>
)
}
export default App
Your font files can be colocated inside of pages
. But, putting them alongside your assets causes a bit less clutter. To make things a bit clearer, this is the folder structure used for the solution above:
src
ββ assets
β ββ fonts
β β ββ my-font-regular.woff2
β β ββ my-font-bold.woff2
ββ pages
β ββ _app.tsx
After this, you should use the CSS variable --font-base
in any global or module-specific stylesheets:
body {
font-family: var(--font-base);
}
If your Next dev server was running during all of this setup you may get the error below. As stated, just restart your dev server to resolve it.
Error: @next/font/local failed to run or is incorrectly configured.
If you just installed `@next/font`, please try restarting `next dev` and resaving your file.
If all goes to plan and your dev server is up and running again, you should see your fonts being applied to your UI π
Verifying that your fonts load and optimizations work
To verify that everything works the way it should, you can inspect the output HTML with your browser devtools.
- Inspect the network tab to check that the font files load
- Check that the CSS variable is set to root, and that it references a fallback font
- Check that
@font-face
declarations are set with matching names
You should see that the correct CSS variable is set in <style>
tags added to <head>
.
<style>
:root {
--font-base: '__fontBase', Helvetica, ui-sans-serif,
'__fontBase_Fallback';
}
</style>
Also added to <head>
you should see multiple @font-face
declarations referencing the same name added to --font-base
, and the files loaded in the network tab.
The last @font-face
declaration is special as it declares a fallback font to enable zero layout shift with size-adjust
while users wait for your custom font to load.
<style>
@font-face {
font-family: '__fontBase';
src: url(/_next/static/media/88i0c441c.p.woff2) format('woff2');
font-display: optional;
font-weight: 300;
}
@font-face {
font-family: '__fontBase';
src: url(/_next/static/media/63a0b432c.p.woff2) format('woff2');
font-display: optional;
font-weight: 600;
}
@font-face {
font-family: '__fontBase_Fallback';
src: local('Arial');
ascent-override: 98.02%;
descent-override: 26.35%;
line-gap-override: 0%;
size-adjust: 101.72%;
}
</style>
These little tweaks will make your CSS cleaner and more flexible, and your app will load faster with little to no layout shift. Ultimately giving your users a more enjoyable experience.