Verified against Claude Code · 2026-07-31
Self-host fonts with next/font without the layout-shift and FOIT tradeoffs
Sets up next/font to self-host and subset fonts at build time with the right fallback metrics and font-display behavior, instead of a runtime Google Fonts request or an unconfigured default that still shifts layout.
The prompt
Ready to copy — highlighted parts are example details you can swap.
You are setting up font loading for a Next.js app using next/font, so fonts are self-hosted and optimized at build time rather than fetched from a runtime request to an external font host, and configured to actually avoid the layout shift and invisible-text tradeoffs a naive setup still produces. FONTS NEEDED Inter at 400 and 600 weight for body and UI text; a display serif (Fraunces) at 500 weight for headings USAGE CONTEXT Inter is used app-wide for body copy and UI chrome; Fraunces is used only for h1/h2 headings on marketing pages CURRENT FONT LOADING A <link> tag to Google Fonts' CDN in the root layout's <head>, loading Inter at all nine available weights STYLING APPROACH Tailwind CSS with a custom theme extending fontFamily SETUP RULES Use next/font/google or next/font/local rather than a manually added <link> tag or an @import in a CSS file pointed at an external font host — next/font downloads the font files at build time and self-hosts them alongside the app's other static assets, which removes the runtime network request to the font provider entirely and means no request to an external domain ever happens at page load, which is also why there's no need to add that domain to any connect-src or similar allowlist. Only load the specific font weights and styles the design actually uses, not the full available range by default — each additional weight is a separate font file that has to be fetched and parsed, and a page that only ever renders 400 and 600 weight text gains nothing from also loading 300, 500, and 700. Configure a fallback font stack with adjustFontFallback left enabled, which is the default, so next/font can automatically adjust the fallback system font's metrics — its size, its line height — to closely match the actual web font's metrics; this is what prevents the visible reflow that happens when a fallback font displays first and then gets swapped for the real font once it loads, since a fallback with mismatched metrics changes how much space the same text occupies the moment the swap happens. If the styling approach uses CSS variables, generate the font as a CSS variable via the variable option and reference it in the site's Tailwind config or global CSS, rather than applying the font's generated className prop by hand to every component that needs it — a single CSS variable applied once at the root layout covers the whole tree, while a className applied per-component is easy to forget on a new component added later. For local, self-hosted font files not available through Google Fonts, use next/font/local with the correct format and weight declarations matching the actual files provided, and confirm variable font files are declared as a single font-weight range rather than as several separate static-weight entries, if the file itself is genuinely variable. OUTPUT FORMAT The font configuration file (or the relevant section of layout.tsx), the CSS variable or className wiring into the root layout, and one paragraph confirming that no runtime request to any external font host occurs anywhere in the resulting setup.
Customize
Optional — swap in your own details for the highlighted parts above.
Why this works
next/font's actual mechanism is a build-time download and self-host, not a runtime optimization layered on top of an external request — the font files get fetched once, during the build, and served from the app's own domain from then on, which is why a setup using next/font correctly has zero runtime connections to any font provider at all, closing off an entire category of privacy and performance concern that a <link> tag to an external CDN carries by design, since every visitor's browser would otherwise make its own separate request to that third-party host. Loading only the weights actually used in the design targets a cost that's invisible in a visual review of the rendered page — a page that visually uses only two weights but has five loaded looks completely correct, and the extra three weights' cost shows up only in the network tab as extra font files downloaded for text that was never going to render in those weights anywhere on the page. The adjustFontFallback behavior is the specific, documented mechanism that prevents layout shift during a font swap: without matched fallback metrics, the browser initially lays out text using a system font's size and line-height, and when the real web font finishes loading and swaps in, any difference between the two fonts' actual character widths and line heights shifts every element positioned relative to that text — adjustFontFallback works by generating an adjusted fallback font specifically calibrated to approximate the real font's metrics as closely as possible, so the swap changes how the text looks without changing how much space it occupies, which is what actually eliminates the visible jump rather than just making it happen sooner. The CSS-variable wiring recommendation over a per-component className exists because a className has to be manually reapplied everywhere a font is used, and a new component added six months later by someone unfamiliar with the setup will very plausibly render in the browser's default font simply because nobody remembered to import and apply the className — a CSS variable set once at the root and referenced through the site's existing CSS or Tailwind config inherits automatically, with no per-component step that can be forgotten.
What you get back
const inter = Inter({ subsets: ['latin'], weight: ['400', '600'], variable: '--font-inter' }); const fraunces = Fraunces({ subsets: ['latin'], weight: ['500'], variable: '--font-fraunces' }); // applied once in app/layout.tsx: <html className={${inter.variable} ${fraunces.variable}}> // Tailwind config: fontFamily.sans = ['var(--font-inter)'], fontFamily.display = ['var(--font-fraunces)'] No runtime font requests: both fonts are downloaded once at build time and served as static assets from the app's own domain — the network tab on any page shows zero requests to fonts.googleapis.com or fonts.gstatic.com.
Verified against
Claude Code Sonnet 4.6 · 2026-07-31
v0 by Vercel 2026.7 · 2026-08-07
Changelog
- 2026-07-31 — Initial publish, verified against Claude Code (Sonnet 4.6) and v0 by Vercel on a Next.js 16 app migrating off a Google Fonts <link> tag.
Need this built into your business?
If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.
EXPLORE CUSTOM SOFTWARE
