How to Fix: The font-family is distorted in nextjs15
Next.js 15 font distortion usually comes from mixing next/font with the wrong CSS variable or applying the font class at the wrong level in the App Router.
If the text looks slightly off, falls back to another typeface, or renders with inconsistent weight and spacing, the problem is rarely the font file itself. In most Next.js 15 cases, the issue is caused by how the font is loaded in app/layout.tsx, how the generated class name is attached to the DOM, or how Tailwind/CSS references the font variable.
Understanding the Root Cause
In Next.js 15, fonts loaded with next/font/google or next/font/local are not meant to be treated like plain string font-family declarations. They generate either a className or a CSS variable, and the browser will only render the intended typeface if that generated output is wired correctly into the root layout.
The distortion typically happens for one of these reasons:
- The font loader is configured with a variable, but the app uses className incorrectly or never references the variable in CSS.
- The font class is attached to a nested element instead of the root <html> or <body>, causing fallback fonts to render during hydration.
- Tailwind is configured to use a custom font family such as font-sans, but that mapping does not point to the generated var(–font-…) token.
- A global stylesheet overrides the font with another font-family rule later in the cascade.
- The selected font weights do not match the actual rendered text, so the browser synthesizes weight or style, making the text look stretched, blurry, or inconsistent.
In the reproduction linked in the issue, the visible symptom is “distorted font-family,” but the underlying problem is usually a mismatch between font registration and font application. When Next.js injects the font correctly but the CSS consumes it incorrectly, the browser falls back or synthesizes rendering in a way that looks broken.
Step-by-Step Solution
The safest fix is to define the font once in app/layout.tsx, expose it as either a direct class or a CSS variable, and then apply it consistently.
1. Load the font correctly in app/layout.tsx
import './globals.css'
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 (
<html lang="en" className={inter.variable}>
<body>{children}</body>
</html>
)
}
This pattern exposes the font as a reusable CSS variable. Applying it on <html> ensures the variable exists globally.
2. Reference the generated variable in globals.css
:root {
--app-font: var(--font-inter);
}
html,
body {
font-family: var(--app-font), Arial, Helvetica, sans-serif;
}
This is the most explicit setup. It prevents accidental fallback to another font and keeps the stack stable across the full application.
3. If you use Tailwind, map the variable in tailwind.config.ts
import type { Config } from 'tailwindcss'
const config: Config = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)', 'Arial', 'Helvetica', 'sans-serif'],
},
},
},
plugins: [],
}
export default config
Then use your normal Tailwind classes:
export default function Page() {
return <main className="font-sans">Hello world</main>
}
4. Alternative: use the generated className directly
If you do not need a CSS variable, use the font loader’s className instead:
import './globals.css'
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
})
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
)
}
This works well when you want one global font and do not need Tailwind variable integration.
5. Remove conflicting global CSS
Check globals.css for rules like these:
body {
font-family: sans-serif;
}
* {
font-family: inherit;
}
p, span, div {
font-family: some-other-font;
}
If a later rule overrides the generated Next.js font, the final render can look distorted or inconsistent. Keep one clear source of truth.
6. Use real weights that the font loader supports
const inter = Inter({
subsets: ['latin'],
weight: ['400', '500', '700'],
display: 'swap',
variable: '--font-inter',
})
If your UI uses font-weight: 500 but only 400 is loaded, the browser may synthesize a weight that looks visually wrong.
7. Final recommended layout for this issue
import './globals.css'
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
weight: ['400', '500', '700'],
display: 'swap',
variable: '--font-inter',
})
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={inter.variable}>
<body>{children}</body>
</html>
)
}
html,
body {
font-family: var(--font-inter), Arial, Helvetica, sans-serif;
}
If you want to compare your setup against the original reproduction, open the linked CodeSandbox example and verify the font is applied exactly once at the root.
Common Edge Cases
- Using variable but forgetting to consume it: Defining variable: ‘–font-inter’ does nothing unless your CSS or Tailwind uses var(–font-inter).
- Applying the class on the wrong element: If the font is attached only to a component subtree, parts of the page may still use a fallback font.
- Duplicate font-family declarations: A component library, CSS reset, or imported stylesheet may silently override the root font.
- Weight mismatch: If you render bold text without loading that weight, the browser may fake it and the result can appear distorted.
- Mixing local and Google fonts: If both define the same semantic utility like font-sans, one may override the other unexpectedly.
- Hydration differences: If server-rendered markup gets one font stack and the client gets another, text can shift or look visually inconsistent after load.
- Browser font smoothing differences: On some systems, a fallback font can look close enough to be confusing, but spacing and thickness will still reveal that the intended font was not applied.
FAQ
Why does the font look wrong even though Next.js loads without errors?
Because the issue is often not loading failure but application failure. The font file can be downloaded correctly while your CSS still points to another font stack or ignores the generated variable.
Should I use className or variable from next/font?
Use className for the simplest global setup. Use variable when integrating with Tailwind, design tokens, or multiple font families. The key is to stay consistent and not mix both patterns incorrectly.
Why does the text look bolder or thinner in Next.js 15?
That usually means the requested font weight was not loaded, so the browser synthesized the style. Load the actual weights you use, such as 400, 500, and 700.
Bottom line: this bug is usually fixed by attaching the next/font output at the root, referencing the correct CSS variable or className, and removing conflicting font-family declarations.