Fixing next/font Google Font Loading Failures in Next.js
Step-by-step guide to diagnosing and resolving next/font issues when Google Fonts fail to load in Next.js applications
Fixing next/font Google Font Loading Failures in Next.js
Introduction
The next/font module is supposed to make Google Font loading fast and reliable. It self-hosts fonts at build time, eliminates layout shift, and optimizes font loading with CSS size-adjust. In theory, it should just work. In practice, I have seen developers struggle with fonts silently falling back to system fonts, build errors from font imports, and incorrect font rendering in production.
After setting up next/font on three separate projects this year, I have compiled a list of every failure mode I have encountered and how to fix them.
Environment
- OS: Windows 11
- Node.js: v20.10.0
- Next.js: 14.0.4
- Fonts: Inter, Roboto Mono, Noto Sans KR
Problem
After configuring next/font/google, the application exhibited the following issues:
Build Error:
Module not found: Can't resolve 'next/font/google'Warning: Your page is missing one or more preconnect tagsFOUT detected: Fonts flash from system font to Google FontIn the browser Network tab, the font CSS request returned a 403:
GET https://fonts.googleapis.com/css2?family=Inter:wght@400;700 403 (Forbidden)Analysis
1. Module resolution failure. This typically happens when the project uses an older version of Next.js or has a misconfigured tsconfig.json that does not include the Next.js type paths.
2. Missing preconnect. When next/font cannot self-host the font (usually in development mode or when the Google Fonts API is unreachable), it falls back to external loading, which requires preconnect hints.
3. FOUT (Flash of Unstyled Text). This occurs when the font CSS is loaded asynchronously but the fallback font has significantly different metrics than the Google Font.
4. 403 from Google Fonts API. This happens when requests come from certain regions or when the API detects automated requests. Running behind a corporate proxy can trigger this.
Solution
Fix 1: Ensure correct module import
// app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}Fix 2: Use local font files as a fallback
When Google Fonts API is unreliable (common in certain regions), download the font files and use next/font/local:
# Download font files to your project
mkdir -p public/fonts
# Download Inter Regular and Bold from Google Fonts
curl -o public/fonts/Inter-Regular.woff2 "https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZ9hiA.woff2"
curl -o public/fonts/Inter-Bold.woff2 "https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuI6fAZ9hiA.woff2"// app/layout.tsx
import localFont from 'next/font/local'
const inter = localFont({
src: [
{ path: '../public/fonts/Inter-Regular.woff2', weight: '400' },
{ path: '../public/fonts/Inter-Bold.woff2', weight: '700' },
],
display: 'swap',
variable: '--font-inter',
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}Fix 3: Handle multiple font families
// app/layout.tsx
import { Inter, Noto_Sans_KR, Roboto_Mono } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
})
const notoSansKR = Noto_Sans_KR({
subsets: ['latin'],
weight: ['400', '700'],
display: 'swap',
variable: '--font-noto-kr',
})
const robotoMono = Roboto_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-roboto-mono',
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}Fix 4: Use fonts in Tailwind CSS
// tailwind.config.ts
import type { Config } from 'tailwindcss'
const config: Config = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)', 'var(--font-noto-kr)', 'sans-serif'],
mono: ['var(--font-roboto-mono)', 'monospace'],
},
},
},
plugins: [],
}
export default configLessons Learned
Always use
display: 'swap'to prevent invisible text while fonts load. The alternativeoptionalcan cause text to never show the web font on slow connections.Download critical font files for production. Relying on Google Fonts API in production introduces an external dependency that can fail. Self-hosting eliminates this risk.
Test with slow network throttling. Font loading issues are often invisible on fast connections. Use Chrome DevTools to simulate 3G and verify font behavior.
Subset fonts aggressively. Only include the character sets you actually need. Including
cyrillicwhen your content is English-only wastes bandwidth.Monitor font loading with the Font Loading API:
// lib/fontMonitor.ts
export async function waitForFonts() {
try {
await document.fonts.ready
console.log('All fonts loaded successfully')
} catch (error) {
console.warn('Font loading failed, using fallback:', error)
}
}This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.