Lesson 22 of 81
A `<link>` to Google Fonts looks free, but it costs a render-blocking network round trip, a flash of invisible or swapped text, and a privacy leak of your visitor's IP to Google. next/font removes all three by turning fonts into a build-time asset.
9 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
A team loads the same Google Font by calling `Inter({...})` separately in two different component files instead of sharing one exported instance. What's the actual consequence?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
next/font downloads and self-hosts font files as part of your build, instead of asking the browser to fetch them from Google Fonts (or another CDN) at request time. For next/font/google, the CSS and font files are downloaded once at build time and served from your own app alongside your other static assets — no request is ever sent to Google by the browser, which removes both the privacy leak and the extra DNS/connection round trip a <link href="fonts.googleapis.com"> tag costs you. next/font/local does the same self-hosting for a font file you already own, no network fetch involved at all, ever.
The other half of the win is layout shift. Because the font loader knows the font's metrics at build time, Next.js can inject a size-adjusted fallback font automatically, so the page doesn't visibly reflow once the real font finishes loading — this is what "no layout shift" from font loading actually means in practice, not just "the font loads fast."
// app/fonts.ts
import { Inter, Roboto_Mono } from "next/font/google";
export const inter = Inter({
subsets: ["latin"],
display: "swap",
variable: "--font-inter",
});
export const robotoMono = Roboto_Mono({
subsets: ["latin"],
display: "swap",
variable: "--font-roboto-mono",
});
// app/layout.tsx
import { inter, robotoMono } from "./fonts";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
);
}
/* app/globals.css */
@theme inline {
--font-sans: var(--font-inter);
--font-mono: var(--font-roboto-mono);
}
Calling Inter(...) doesn't return a font, it returns an object with a className, a style, and here a variable — the CSS custom property name Next.js will declare and fill with the resolved font-family stack (real font, plus the auto-generated size-adjusted fallback). Applying inter.variable to <html> makes --font-inter available everywhere below it, which is what the Tailwind @theme inline block wires into font-sans. This variable approach is the one worth defaulting to over className: it lets you keep font selection in your CSS/Tailwind config rather than threading a className prop through every component that needs a specific typeface.
Roboto_Mono, not RobotoMono or "Roboto Mono". Forgetting this is a silent import failure, not a helpful error.display defaults to 'swap', not 'auto' or 'block' — text renders immediately in a fallback font and swaps to the real one once it loads, rather than staying invisible while waiting. This is usually what you want, but it's worth knowing it's the framework's choice, not a browser default."Why would you use next/font instead of a Google Fonts <link> tag?" tests whether a candidate can name the actual mechanisms — self-hosting removing a runtime network request, and build-time metrics removing layout shift — rather than a vague "it's faster" that doesn't survive a follow-up question.