W Metropolis Quantum, miejscy projektanci wiedzą, że typografia jest kluczowym elementem tożsamości wizualnej miasta. Każdy znak, informacja czy interfejs miejskich terminali wykorzystuje staranne dobrane kroje pisma, które są nie tylko estetyczne, ale również zoptymalizowane pod kątem szybkości wyświetlania i czytelności na różnych urządzeniach. W świecie webowym, czcionki pełnią podobną rolę - są istotnym elementem projektowym, ale mogą też znacząco wpływać na wydajność stron internetowych.
Next.js 15 wprowadza nowy, zoptymalizowany sposób zarządzania czcionkami poprzez komponent
next/font. Ten rozdział poświęcimy poznaniu tego rozwiązania oraz najlepszym praktykom optymalizacji fontów w aplikacjach Next.js.Niestandardowe czcionki internetowe mogą znacząco wpłynąć na wydajność stron:
Nowo wprowadzony system fontów w Next.js 15 rozwiązuje te problemy, zapewniając:
Next.js 15 dostarcza moduł
next/font, który obsługuje:next/font/google)next/font/local)Oto jak używać czcionek Google w Next.js 15:
1// app/fonts.ts
2import { Inter, Roboto_Mono } from 'next/font/google';
3
4// Zmienne eksportowe pozwalają na ponowne wykorzystanie tych samych instancji fontów
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});Następnie możesz użyć tych czcionek w swoim layoucie:
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: 'Oficjalny portal informacyjny miasta przyszłości',
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}Możesz również używać lokalnych plików czcionek, co jest zalecane w przypadku własnych, niestandardowych krojów pisma:
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'], // Wybranie konkretnych wag (grubości) czcionki
5 style: ['normal', 'italic'], // Wybranie stylów czcionki
6 subsets: ['latin'], // Wybranie podzbiorów znaków
7 display: 'swap', // Strategia wyświetlania: 'auto', 'block', 'swap', 'fallback', 'optional'
8 preload: true, // Czy preładować czcionkę
9 fallback: ['system-ui', 'Arial'], // Czcionki zapasowe
10 adjustFontFallback: true, // Automatyczne dostosowywanie czcionek zapasowych
11 variable: '--font-roboto', // Zmienna CSS, jeśli używamy API zmiennych
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}Jeśli Twoja aplikacja obsługuje wiele języków, możesz zoptymalizować pliki fontów, ładując tylko potrzebne podzestawy znaków:
1import { Roboto } from 'next/font/google';
2
3// Dla strony po polsku
4export const robotoPolish = Roboto({
5 subsets: ['latin', 'latin-ext'], // latin-ext zawiera znaki używane w języku polskim
6 weight: ['400', '700'],
7});
8
9// Dla strony po japońsku
10export const robotoJapanese = Roboto({
11 subsets: ['latin', 'japanese'],
12 weight: ['400', '700'],
13});Czcionki zmienne to nowoczesny format fontów, który pozwala na zapisanie wielu stylów i wag w jednym pliku, co znacznie zmniejsza ilość danych do pobrania:
1import { Roboto_Flex } from 'next/font/google';
2
3export const roboto = Roboto_Flex({
4 subsets: ['latin'],
5 display: 'swap',
6 // Variable fonts często oferują szerszy zakres wag
7});Domyślnie Next.js preładuje czcionki, ale możesz wyłączyć tę funkcję dla czcionek, które nie są używane natychmiast:
1import { Roboto } from 'next/font/google';
2
3// Główna czcionka, preładowana
4export const robotoMain = Roboto({
5 subsets: ['latin'],
6 weight: ['400', '700'],
7 preload: true,
8});
9
10// Czcionka używana tylko w niektórych komponentach, bez preładowania
11export const robotoSpecial = Roboto({
12 subsets: ['latin'],
13 weight: ['900'],
14 preload: false,
15});Właściwość
display określa, jak przeglądarka powinna obsługiwać etap ładowania czcionki:swap (domyślne): Natychmiast pokazuje tekst używając czcionki fallback, zamienia ją na niestandardową czcionkę, gdy ta się załaduje.block: Krótko ukrywa tekst, a następnie pokazuje go z czcionką fallback, zamienia na docelową po załadowaniu.fallback: Podobne do swap, ale z krótkim okresem ukrytego tekstu. Jeśli ładowanie trwa zbyt długo, używana jest czcionka fallback na czas sesji.optional: Podobne do fallback, ale przeglądarka może całkowicie pominąć niestandardową czcionkę, bazując na warunkach sieciowych.1import { Roboto } from 'next/font/google';
2
3// Dla głównego tekstu, używamy swap, aby zapewnić natychmiastową czytelność
4export const robotoBody = Roboto({
5 subsets: ['latin'],
6 weight: ['400'],
7 display: 'swap',
8});
9
10// Dla ozdobnych nagłówków, możemy użyć optional, gdyż nie są one krytyczne
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}Przykład użycia:
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 Miasto przyszłości, w którym technologia spotyka się z codziennością.
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}Odpowiednia implementacja fontów ma bezpośredni wpływ na kluczowe metryki wydajności:
Czcionki niestandardowe mogą opóźnić rendering największego elementu treści. Komponent Font w Next.js 15 minimalizuje ten problem przez:
Automatyczne obliczanie rozmiarów fontów fallbackowych minimalizuje przesunięcia układu, gdy niestandardowa czcionka jest ostatecznie ładowana.
Czcionki nie mają bezpośredniego wpływu na te metryki, ale efektywne ładowanie fontów może zmniejszyć obciążenie wątku głównego przeglądarki.
Używaj narzędzia Lighthouse do analizy wydajności czcionek:
Analizuj ładowanie czcionek w zakładce Network:
Narzędzie WebPageTest (webpagetest.org) oferuje szczegółową analizę ładowania zasobów, w tym czcionek, na różnych urządzeniach i łączach internetowych.
display) w zależności od przeznaczenia czcionkiPoniżej znajduje się przykład kompleksowej implementacji systemu fontów w aplikacji Next.js 15:
1// app/fonts.ts
2import { Inter, Playfair_Display, Roboto_Mono } from 'next/font/google';
3import localFont from 'next/font/local';
4
5// Główna czcionka dla tekstu
6export const inter = Inter({
7 subsets: ['latin', 'latin-ext'],
8 display: 'swap',
9 variable: '--font-sans',
10 weight: ['400', '500', '700'],
11});
12
13// Czcionka dla nagłówków
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// Własna, niestandardowa czcionka (logo i wyróżnione elementy)
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 /* Rozmiary fontów */
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}Rozwiązanie: Używaj
display: 'swap' i upewnij się, że czcionki są preładowane.Rozwiązanie:
Rozwiązanie:
display: 'optional'Rozwiązanie: Next.js 15 automatycznie dopasowuje metryki czcionek fallbackowych do docelowych czcionek, minimalizując przesunięcia. Możesz też użyć
adjustFontFallback: true.Komponent Font w Next.js 15 automatycznie dostarcza czcionki w formacie WOFF2, który jest wspierany przez wszystkie nowoczesne przeglądarki. Dla starszych przeglądarek, które nie obsługują WOFF2, wyświetlane są czcionki fallbackowe zdefiniowane w konfiguracji.
Przy pracy z aplikacjami wielojęzycznymi, pamiętaj o:
1// Przykład dla aplikacji wielojęzycznej
2import { Noto_Sans_JP, Noto_Sans } from 'next/font/google';
3
4// Dla języka łacińskiego (np. angielski, polski)
5export const notoSansLatin = Noto_Sans({
6 subsets: ['latin', 'latin-ext'],
7 weight: ['400', '700'],
8 variable: '--font-latin',
9});
10
11// Dla języka japońskiego
12export const notoSansJP = Noto_Sans_JP({
13 subsets: ['japanese'],
14 weight: ['400', '700'],
15 variable: '--font-japanese',
16});Komponent Font w Next.js 15 zapewnia kompleksowe rozwiązanie dla zarządzania i optymalizacji czcionek w aplikacjach webowych:
Podobnie jak w Metropolis Quantum, gdzie każdy aspekt wizualnej komunikacji jest starannie zaprojektowany i zoptymalizowany, dobrze zaimplementowany system czcionek w Next.js 15 zapewnia idealną równowagę między estetyką a wydajnością. Dzięki komponentowi Font, aplikacje Next.js mogą oferować spójne, piękne i efektywne doświadczenie typograficzne bez uszczerbku dla wydajności.
W następnym rozdziale omówimy zarządzanie zasobami statycznymi w katalogu
public, co pozwoli nam lepiej zrozumieć, jak efektywnie organizować i obsługiwać różne typy zasobów w aplikacjach Next.js 15.