In Metropolis Quantum, city designers know that typography is a key element of the city's visual identity. Every sign, information display, and urban terminal interface uses carefully selected typefaces that are not only aesthetic but also optimized for rendering speed and readability on various devices. In the web world, fonts play a similar role - they are an important design element but can also significantly affect website performance.
Next.js 15 introduces a new, optimized way of managing fonts through the
next/font component. In this chapter, we will explore this solution and best practices for font optimization in Next.js applications.Custom web fonts can significantly impact page performance:
The newly introduced font system in Next.js 15 solves these problems by providing:
Next.js 15 provides the
next/font module, which supports:next/font/google)next/font/local)Here is how to use Google Fonts in Next.js 15:
1// app/fonts.ts
2import { Inter, Roboto_Mono } from 'next/font/google';
3
4// Export variables allow reuse of the same font instances
5export const inter = Inter({
6 subsets: ['latin'],
7 display: 'swap',
8});
9
10export const roboto_mono = Roboto_Mono({
11 subsets: ['latin'],
12 display: 'swap',
13 weight: ['400', '700'],
14});Then you can use these fonts in your layout:
1// app/layout.tsx
2import { inter } from './fonts';
3import './globals.css';
4import type { Metadata } from 'next';
5
6export const metadata: Metadata = {
7 title: 'Metropolis Quantum',
8 description: 'Official information portal of the city of the future',
9};
10
11export default function RootLayout({
12 children,
13}: {
14 children: React.ReactNode;
15}) {
16 return (
17 <html lang="pl" className={inter.className}>
18 <body>{children}</body>
19 </html>
20 );
21}You can also use local font files, which is recommended for custom, non-standard typefaces:
1// app/fonts.ts
2import localFont from 'next/font/local';
3
4export const quantumSans = localFont({
5 src: [
6 {
7 path: '../public/fonts/QuantumSans-Regular.woff2',
8 weight: '400',
9 style: 'normal',
10 },
11 {
12 path: '../public/fonts/QuantumSans-Bold.woff2',
13 weight: '700',
14 style: 'normal',
15 },
16 {
17 path: '../public/fonts/QuantumSans-Italic.woff2',
18 weight: '400',
19 style: 'italic',
20 },
21 ],
22 display: 'swap',
23 variable: '--font-quantum-sans',
24});Komponent Font w Next.js 15 oferuje szereg opcji konfiguracyjnych:
1import { Roboto } from 'next/font/google';
2
3const roboto = Roboto({
4 weight: ['400', '700'], // Selecting specific font weights
5 style: ['normal', 'italic'], // Selecting font styles
6 subsets: ['latin'], // Selecting character subsets
7 display: 'swap', // Display strategy: 'auto', 'block', 'swap', 'fallback', 'optional'
8 preload: true, // Whether to preload the font
9 fallback: ['system-ui', 'Arial'], // Czcionki zapasowe
10 adjustFontFallback: true, // Automatyczne dostosowywanie czcionek zapasowych
11 variable: '--font-roboto', // CSS variable if using variable API
12});1import localFont from 'next/font/local';
2
3const quantumDisplay = localFont({
4 src: [
5 {
6 path: './fonts/QuantumDisplay-Regular.woff2',
7 weight: '400',
8 style: 'normal',
9 },
10 {
11 path: './fonts/QuantumDisplay-Bold.woff2',
12 weight: '700',
13 style: 'normal',
14 },
15 ],
16 display: 'swap',
17 preload: true,
18 variable: '--font-quantum-display',
19 fallback: ['system-ui', 'Arial'],
20 adjustFontFallback: true,
21});1// app/layout.tsx
2import { quantumSans } from './fonts';
3import './globals.css';
4
5export default function RootLayout({
6 children,
7}: {
8 children: React.ReactNode;
9}) {
10 return (
11 <html lang="pl" className={quantumSans.className}>
12 <body>{children}</body>
13 </html>
14 );
15}1// app/fonts.ts
2import { Inter, Roboto_Mono } from 'next/font/google';
3
4export const inter = Inter({
5 subsets: ['latin'],
6 display: 'swap',
7 variable: '--font-inter',
8});
9
10export const roboto_mono = Roboto_Mono({
11 subsets: ['latin'],
12 display: 'swap',
13 weight: ['400', '700'],
14 variable: '--font-roboto-mono',
15});1// app/layout.tsx
2import { inter, roboto_mono } from './fonts';
3import './globals.css';
4
5export default function RootLayout({
6 children,
7}: {
8 children: React.ReactNode;
9}) {
10 return (
11 <html lang="pl" className={`${inter.variable} ${roboto_mono.variable}`}>
12 <body>{children}</body>
13 </html>
14 );
15}1/* app/globals.css */
2:root {
3 --font-inter: 'Inter', system-ui, sans-serif;
4 --font-roboto-mono: 'Roboto Mono', monospace;
5}
6
7body {
8 font-family: var(--font-inter);
9}
10
11code {
12 font-family: var(--font-roboto-mono);
13}1// components/Heading.tsx
2import { quantumDisplay } from '@/app/fonts';
3
4export function Heading({ children }: { children: React.ReactNode }) {
5 return (
6 <h1 className={quantumDisplay.className}>
7 {children}
8 </h1>
9 );
10}If your application supports multiple languages, you can optimize font files by loading only the needed character subsets:
1import { Roboto } from 'next/font/google';
2
3// Dla strony po polsku
4export const robotoPolish = Roboto({
5 subsets: ['latin', 'latin-ext'], // latin-ext contains characters used in Polish
6 weight: ['400', '700'],
7});
8
9// For a Japanese page
10export const robotoJapanese = Roboto({
11 subsets: ['latin', 'japanese'],
12 weight: ['400', '700'],
13});Variable fonts are a modern font format that allows storing multiple styles and weights in a single file, significantly reducing the amount of data to download:
1import { Roboto_Flex } from 'next/font/google';
2
3export const roboto = Roboto_Flex({
4 subsets: ['latin'],
5 display: 'swap',
6 // Variable fonts often offer a wider range of weights
7});By default, Next.js preloads fonts, but you can disable this feature for fonts that are not used immediately:
1import { Roboto } from 'next/font/google';
2
3// Main font, preloaded
4export const robotoMain = Roboto({
5 subsets: ['latin'],
6 weight: ['400', '700'],
7 preload: true,
8});
9
10// Font used only in some components, without preloading
11export const robotoSpecial = Roboto({
12 subsets: ['latin'],
13 weight: ['900'],
14 preload: false,
15});The
display property determines how the browser should handle the font loading phase:swap (default): Immediately shows text using the fallback font, swaps it for the custom font when it loads.block: Briefly hides text, then shows it with the fallback font, swaps to the target after loading.fallback: Similar to swap, but with a short period of hidden text. If loading takes too long, the fallback font is used for the session.optional: Similar to fallback, but the browser may completely skip the custom font based on network conditions.1import { Roboto } from 'next/font/google';
2
3// For main text, we use swap to ensure immediate readability
4export const robotoBody = Roboto({
5 subsets: ['latin'],
6 weight: ['400'],
7 display: 'swap',
8});
9
10// For decorative headings, we can use optional since they are not critical
11export const robotoHeading = Roboto({
12 subsets: ['latin'],
13 weight: ['900'],
14 display: 'optional',
15});1// app/fonts.ts
2import { Inter, Playfair_Display } from 'next/font/google';
3
4export const inter = Inter({
5 subsets: ['latin'],
6 display: 'swap',
7 variable: '--font-sans',
8});
9
10export const playfair = Playfair_Display({
11 subsets: ['latin'],
12 display: 'swap',
13 variable: '--font-serif',
14});1// app/layout.tsx
2import { inter, playfair } from './fonts';
3import './globals.css';
4
5export default function RootLayout({
6 children,
7}: {
8 children: React.ReactNode;
9}) {
10 return (
11 <html lang="pl" className={`${inter.variable} ${playfair.variable}`}>
12 <body>
13 <header className={playfair.className}>
14 <h1>Metropolis Quantum</h1>
15 </header>
16 <main className={inter.className}>
17 {children}
18 </main>
19 </body>
20 </html>
21 );
22}1/* app/globals.css */
2:root {
3 --font-sans: 'Inter', system-ui, sans-serif;
4 --font-serif: 'Playfair Display', Georgia, serif;
5}
6
7h1, h2, h3, h4, h5, h6 {
8 font-family: var(--font-serif);
9}
10
11body {
12 font-family: var(--font-sans);
13}1// app/dashboard/layout.tsx
2import { roboto_mono } from '../fonts';
3
4export default function DashboardLayout({
5 children,
6}: {
7 children: React.ReactNode;
8}) {
9 return (
10 <div className="dashboard-layout">
11 <div className="sidebar">
12 {/* Sidebar content */}
13 </div>
14 <div className={`content ${roboto_mono.className}`}>
15 {children}
16 </div>
17 </div>
18 );
19}1// components/Typography.tsx
2import { ReactNode } from 'react';
3import { inter, playfair, roboto_mono } from '@/app/fonts';
4
5type FontVariant = 'sans' | 'serif' | 'mono';
6
7interface TypographyProps {
8 children: ReactNode;
9 variant?: FontVariant;
10 as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'div';
11 className?: string;
12}
13
14export function Typography({
15 children,
16 variant = 'sans',
17 as: Component = 'p',
18 className = '',
19}: TypographyProps) {
20 let fontClass = '';
21
22 switch (variant) {
23 case 'sans':
24 fontClass = inter.className;
25 break;
26 case 'serif':
27 fontClass = playfair.className;
28 break;
29 case 'mono':
30 fontClass = roboto_mono.className;
31 break;
32 }
33
34 return (
35 <Component className={`${fontClass} ${className}`}>
36 {children}
37 </Component>
38 );
39}Example usage:
1// app/page.tsx
2import { Typography } from '@/components/Typography';
3
4export default function HomePage() {
5 return (
6 <div>
7 <Typography as="h1" variant="serif" className="text-4xl mb-4">
8 Witaj w Metropolis Quantum
9 </Typography>
10
11 <Typography variant="sans" className="mb-6">
12 The city of the future where technology meets everyday life.
13 </Typography>
14
15 <div className="code-example">
16 <Typography variant="mono" className="text-sm">
17 console.log("Hello, Quantum World!");
18 </Typography>
19 </div>
20 </div>
21 );
22}Proper font implementation has a direct impact on key performance metrics:
Custom fonts can delay the rendering of the largest content element. The Font component in Next.js 15 minimizes this problem by:
Automatic calculation of fallback font sizes minimizes layout shifts when the custom font is finally loaded.
Fonts do not have a direct impact on these metrics, but efficient font loading can reduce the load on the browser's main thread.
Use the Lighthouse tool to analyze font performance:
Analyze font loading in the Network tab:
The WebPageTest tool (webpagetest.org) offers detailed analysis of resource loading, including fonts, on different devices and internet connections.
display) depending on the font's purposeBelow is an example of a comprehensive font system implementation in a Next.js 15 application:
1// app/fonts.ts
2import { Inter, Playfair_Display, Roboto_Mono } from 'next/font/google';
3import localFont from 'next/font/local';
4
5// Main font for text
6export const inter = Inter({
7 subsets: ['latin', 'latin-ext'],
8 display: 'swap',
9 variable: '--font-sans',
10 weight: ['400', '500', '700'],
11});
12
13// Font for headings
14export const playfair = Playfair_Display({
15 subsets: ['latin', 'latin-ext'],
16 display: 'optional',
17 variable: '--font-serif',
18 weight: ['700', '900'],
19});
20
21// Czcionka dla kodu
22export const roboto_mono = Roboto_Mono({
23 subsets: ['latin'],
24 display: 'swap',
25 variable: '--font-mono',
26 weight: ['400', '700'],
27});
28
29// Custom, non-standard font (logo and highlighted elements)
30export const quantumDisplay = localFont({
31 src: [
32 {
33 path: '../public/fonts/QuantumDisplay-Regular.woff2',
34 weight: '400',
35 style: 'normal',
36 },
37 {
38 path: '../public/fonts/QuantumDisplay-Bold.woff2',
39 weight: '700',
40 style: 'normal',
41 },
42 ],
43 display: 'swap',
44 variable: '--font-quantum',
45});1// app/layout.tsx
2import { inter, playfair, roboto_mono, quantumDisplay } from './fonts';
3import './globals.css';
4
5export default function RootLayout({
6 children,
7}: {
8 children: React.ReactNode;
9}) {
10 return (
11 <html lang="pl" className={`
12 ${inter.variable}
13 ${playfair.variable}
14 ${roboto_mono.variable}
15 ${quantumDisplay.variable}
16 `}>
17 <body className={inter.className}>{children}</body>
18 </html>
19 );
20}1/* app/globals.css */
2:root {
3 --font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
4 --font-serif: 'Playfair Display', Georgia, Cambria, 'Times New Roman', Times, serif;
5 --font-mono: 'Roboto Mono', SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
6 --font-quantum: 'Quantum Display', var(--font-sans);
7
8 /* Font sizes */
9 --fs-xs: 0.75rem;
10 --fs-sm: 0.875rem;
11 --fs-base: 1rem;
12 --fs-lg: 1.125rem;
13 --fs-xl: 1.25rem;
14 --fs-2xl: 1.5rem;
15 --fs-3xl: 1.875rem;
16 --fs-4xl: 2.25rem;
17 --fs-5xl: 3rem;
18
19 /* Line-heights */
20 --lh-tight: 1.25;
21 --lh-snug: 1.375;
22 --lh-normal: 1.5;
23 --lh-relaxed: 1.625;
24 --lh-loose: 2;
25}
26
27body {
28 font-family: var(--font-sans);
29 font-size: var(--fs-base);
30 line-height: var(--lh-normal);
31}
32
33h1, h2, h3, h4, h5, h6 {
34 font-family: var(--font-serif);
35 line-height: var(--lh-tight);
36 font-weight: 700;
37}
38
39h1 {
40 font-size: var(--fs-4xl);
41 margin-bottom: 1.5rem;
42}
43
44h2 {
45 font-size: var(--fs-3xl);
46 margin-bottom: 1.25rem;
47}
48
49.logo {
50 font-family: var(--font-quantum);
51 font-weight: 700;
52}
53
54code, pre {
55 font-family: var(--font-mono);
56 font-size: var(--fs-sm);
57}
58
59.title-quantum {
60 font-family: var(--font-quantum);
61 font-weight: 700;
62 letter-spacing: -0.025em;
63}Solution: Use
display: 'swap' and make sure fonts are preloaded.Solution:
Solution:
display: 'optional'Solution: Next.js 15 automatically matches fallback font metrics to target fonts, minimizing shifts. You can also use
adjustFontFallback: true.The Font component in Next.js 15 automatically delivers fonts in WOFF2 format, which is supported by all modern browsers. For older browsers that do not support WOFF2, fallback fonts defined in the configuration are displayed.
When working with multilingual applications, remember:
1// Example for a multilingual application
2import { Noto_Sans_JP, Noto_Sans } from 'next/font/google';
3
4// For Latin languages (e.g., English, Polish)
5export const notoSansLatin = Noto_Sans({
6 subsets: ['latin', 'latin-ext'],
7 weight: ['400', '700'],
8 variable: '--font-latin',
9});
10
11// For Japanese language
12export const notoSansJP = Noto_Sans_JP({
13 subsets: ['japanese'],
14 weight: ['400', '700'],
15 variable: '--font-japanese',
16});The Font component in Next.js 15 provides a comprehensive solution for managing and optimizing fonts in web applications:
Just like in Metropolis Quantum, where every aspect of visual communication is carefully designed and optimized, a well-implemented font system in Next.js 15 ensures the perfect balance between aesthetics and performance. Thanks to the Font component, Next.js applications can offer a consistent, beautiful, and efficient typographic experience without compromising performance.
In the next chapter, we will discuss managing static assets in the
public directory, which will allow us to better understand how to efficiently organize and serve different types of resources in Next.js 15 applications.