When you build a Next.js app and add custom fonts, the font files often become a critical path resource: slow downloads, layout shifts, or flashes of unstyled text degrade both perceived and objective performance. In earlier setups, you’d include <link>
tags to Google Fonts or host your .woff2
files manually and try to add preload
hints or font-display: swap
rules. But with recent Next.js versions (≥ 14/ 15), there is a built-in next/font system that automates many of the optimizations for you.
In this guide I’ll walk you through how to use next/font (for Google or local fonts), how to tweak fallback and loading behavior, pitfalls to watch for, and how this fits into a deployment on something like a DigitalOcean droplet.
(As a reminder: since you’ll be deploying, make sure your DigitalOcean account is set up in advance—so you can spin up droplets, point DNS, etc.)
Why font optimization matters (again)
Optimizing fonts is not just about “nice typography”—it touches:
- First Contentful Paint (FCP) / Largest Contentful Paint (LCP): extra network requests for fonts delay visible text rendering.
- Cumulative Layout Shift (CLS): when fallback font is rendered, then swapped to your custom font, geometry may change (leading to layout shift).
- Flash of Invisible Text (FOIT) or Flash of Unstyled Text (FOUT): either text remains invisible until font loads, or fallback text is shown then swapped.
- Privacy & external requests: when you link directly to Google Fonts, the browser contacts Google’s servers (IP exposure, possible GDPR considerations).
- Caching & bundling: self-hosting your fonts (served from same domain) allows optimal cache control, fewer DNS lookups, and assured availability.
Next.js’s built-in next/font
system handles many of these: it downloads and bundles font files at build time, self-hosts them, inlines necessary @font-face
CSS to avoid extra request, and uses CSS size-adjust
to minimize layout shift.
Let’s see how to use it, and when to override or augment it.
Basic usage of next/font
in App Router
In a Next.js (v14/15) project using the App Router, here’s how you integrate a Google font:
// app/fonts.ts
import { Inter } from "next/font/google";
export const inter = Inter({
subsets: ["latin"],
// optional: you can specify variable font options or weight/style arrays
});
// app/layout.tsx
import "./globals.css";
import { inter } from "./fonts";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
);
}
Here, next/font/google
will:
- Download the font files (e.g.
.woff2
) at build time. - Generate CSS
@font-face
rules and inline them (so no extra fetch for a CSS file). - Serve the font files from your own deployment (same domain).
- Apply a classname (
inter.className
) that you can put on<html>
or<body>
.
You can also do more advanced options, e.g. specifying weight
, style
, or display
settings. If your font is variable (i.e. multiple weights/styles in one file), that tends to yield smaller size for multiple weights.
If you prefer or need to use a local font file (e.g. custom corporate font), you can do:
// app/fonts.ts
import localFont from "next/font/local";
export const myFont = localFont({
src: [
{
path: "/fonts/MyFont-Regular.woff2",
weight: "400",
style: "normal",
},
{
path: "/fonts/MyFont-Bold.woff2",
weight: "700",
style: "normal",
},
],
variable: "--my-font", // (optional) for using a CSS variable
});
// in layout
<html className={myFont.className}> ...
Because next/font
handles both Google and local fonts, it removes external references and centralizes your font strategy.
Advanced controls, fallbacks, and performance tweaks
While next/font does a lot for you, there are scenarios where you want finer control.
Fallback fonts & metrics matching
Even with self-hosted fonts, users may land on fallback fonts briefly if the custom font hasn’t loaded. Next.js uses size-adjust
behind the scenes to match metrics (so fallback and target font have similar x-height, minimizing layout shift).
If you want explicit control over fallback stacks, you might write:
body {
font-family: "MyCustom", system-ui, sans-serif;
}
But avoid large mismatches (e.g. serif fallback when your font is wide sans) — that invites bigger shifts.
Preload hints and overloading
In earlier setups, you might add <link rel="preload" as="font" href="...">
for critical fonts. But next/font
already injects the optimal preload rules for you, so manual preloading typically is unnecessary. Over-preloading can even hurt by competing for bandwidth with critical page assets.
That said, if you have a very large variable font file that’s not used above the fold, you can choose to delay its loading or load non-critical weights lazily.
Subsetting characters / ranges
If your app targets only specific languages (e.g. Latin, Cyrillic, Vietnamese), you should subset the font to only those ranges. The fewer glyphs you bundle, the smaller the file. In next/font you specify subsets (e.g. subsets: ["latin"]
).
In some advanced cases, people manually create subsets (e.g. only those letters your UI uses) using font tools, but that is often overkill unless your font size is massive.
Loading non-critical weights lazily
If you have weights that are rarely used (e.g. ultra-light, black), you may defer their loading until needed. One strategy: load the core family via next/font, but for additional weights, fetch them at runtime (e.g. with dynamic @import
or injecting another @font-face
via <link>
when a certain UI component mounts).
Be cautious: that dynamic load might introduce FOUT or shift, so test carefully with real usage.
Font caching headers & CDN
Since fonts are static assets, ensure they are served with long cache headers (e.g. Cache-Control: max-age=31536000, immutable
) along with a versioned filename. If you’re using a CDN (e.g. in front of your Droplet or via DigitalOcean Spaces / CDN), ensure your font files are also cached.
Also, mark correct MIME types (e.g. font/woff2
) — misconfigured server may serve wrong content type and browsers might reject or delay those. There’s a known issue when serving fonts from public folder where content-type was wrong on a DigitalOcean/Express setup.
Caveats & pitfalls to watch
- Preloading too many fonts: Some developers warn that preloading too many font files (or all extensions) can backfire by competing with critical resources. One commenter noted that Next.js may preload all font formats even if only one is needed — this overhead may outweigh benefits in some edge cases.
- Non-supported font providers: If you rely on a custom font provider (not Google or your own files), you might lose some of the automation.
- Exceeding budget: If your font file (or its variants) is huge (> several hundred KB) it may dominate your performance budget. Always measure.
- Hosting setup mistakes: If your font files are served through a misconfigured server (e.g. your reverse proxy on DigitalOcean is not forwarding
woff2
content type correctly), you may see errors or mis-headers (as in this StackOverflow issue). - Overriding next/font behavior: If you manually include
<link rel="stylesheet" href="…">
to Google Fonts, you defeat the optimizations next/font provides (extra round trip, no metric matching, etc.). - Fallback to system fonts: In some cases, using system font stacks (e.g.
system-ui, -apple-system, Segoe UI, Roboto…
) avoids font downloads entirely. Use this only if your design tolerates it and your typographic branding is less critical.
Measuring & validation
To ensure your optimization is effective:
- Lighthouse / PageSpeed Insights: check metrics like FCP, LCP, CLS before vs after.
- Network tab (Chrome DevTools): ensure no Google Fonts request is made, font files are loaded from your domain, and are preloaded if critical.
- Web Vitals / Real User Monitoring (RUM): capture real users’ font load times and layout shift.
- Cumulative Layout Shift (CLS): if text shifts when font loads, revisit fallback matching and
size-adjust
settings.
Test across network throttling (e.g. “Slow 3G”) and across diverse devices (mobile, low-end hardware).
Suggested workflow & checklist
Here’s a step-by-step you can adopt when launching a new Next.js project with custom typography:
- Pick your typefaces (Google or local), favor variable fonts when possible.
- Use
next/font/google
ornext/font/local
to integrate them. - Add the generated classname globally (in
app/layout.tsx
). - Limit subsets to only needed character ranges.
- Keep fallback font stacks reasonable and similar in metrics.
- Avoid manual
<link>
to Google Fonts once using next/font. - Serve and cache font files correctly in your server (or CDN) configuration.
- Run performance audits (Lighthouse, WebVitals) to detect font load or layout shift issues.
- For rarely used weights, defer load or dynamically fetch.
- Monitor in production (RUM) for regressions over time.
If you like, I can prepare a ready-to-drop starter project (Next.js + font setup + Nginx config) that you can deploy on DigitalOcean. Just say the word in the comments and I’ll get on top of it.