Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Optymalizacja czcionek i komponent Font w Next.js 15

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.

Dlaczego optymalizacja czcionek jest ważna?

Niestandardowe czcionki internetowe mogą znacząco wpłynąć na wydajność stron:

  1. Wolniejsze ładowanie strony - czcionki internetowe to dodatkowe zasoby, które muszą zostać pobrane
  2. Migotanie tekstu (FOUT - Flash of Unstyled Text) - gdy tekst początkowo wyświetla się domyślną czcionką, a następnie zmienia się na niestandardową po jej załadowaniu
  3. Niewidoczny tekst (FOIT - Flash of Invisible Text) - gdy tekst jest ukryty do momentu załadowania czcionki
  4. Impact na Core Web Vitals - zwłaszcza na Largest Contentful Paint (LCP) i Cumulative Layout Shift (CLS)

Nowo wprowadzony system fontów w Next.js 15 rozwiązuje te problemy, zapewniając:

  • Zero dodatkowych żądań HTTP do zewnętrznych serwerów
  • Automatyczne samohostowanie plików czcionek
  • Automatyczny system fallbacków
  • Brak przesunięć układu (layout shift)
  • Wbudowane podpowiedzi podmiany czcionek dla przeglądarek

Komponent Font w Next.js 15

Next.js 15 dostarcza moduł

next/font
, który obsługuje:

  • Google Fonts (
    next/font/google
    )
  • Lokalne pliki czcionek (
    next/font/local
    )

Google Fonts

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}

Lokalne pliki czcionek

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});

Opcje konfiguracji fontów

Komponent Font w Next.js 15 oferuje szereg opcji konfiguracyjnych:

Dla Google Fonts:

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});

Dla lokalnych czcionek:

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});

Używanie fontów w aplikacji

1. Globalne użycie dla całej aplikacji

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}

2. Używanie zmiennych CSS dla większej elastyczności

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}

3. Używanie dla konkretnych komponentów

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}

Zaawansowane techniki optymalizacji czcionek

1. Optymalizacja subsets dla różnych języków

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});

2. Używanie czcionek zmiennych (variable fonts)

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});

3. Preload vs. Lazy loading

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});

4. Strategie wyświetlania (font-display)

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});

Implementacja w różnych układach

1. Podstawowy układ z dwiema czcionkami

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}

2. Dashboard z czcionką monospacjową dla danych

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}

3. System komponentów typograficznych

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}

Wpływ na Core Web Vitals

Odpowiednia implementacja fontów ma bezpośredni wpływ na kluczowe metryki wydajności:

Largest Contentful Paint (LCP)

Czcionki niestandardowe mogą opóźnić rendering największego elementu treści. Komponent Font w Next.js 15 minimalizuje ten problem przez:

  • Automatyczne samohostowanie fontów
  • Preładowanie krytycznych fontów
  • Strategie wyświetlania umożliwiające natychmiastowe wyświetlanie tekstu

Cumulative Layout Shift (CLS)

Automatyczne obliczanie rozmiarów fontów fallbackowych minimalizuje przesunięcia układu, gdy niestandardowa czcionka jest ostatecznie ładowana.

First Input Delay (FID) i Interaction to Next Paint (INP)

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.

Analizowanie wydajności czcionek

1. Lighthouse

Używaj narzędzia Lighthouse do analizy wydajności czcionek:

  1. Otwórz narzędzia deweloperskie Chrome (F12)
  2. Przejdź do zakładki "Lighthouse"
  3. Zaznacz "Performance" i uruchom test
  4. Sprawdź sekcję "Opportunities" i "Diagnostics" pod kątem potencjalnych problemów z czcionkami

2. Network panel w DevTools

Analizuj ładowanie czcionek w zakładce Network:

  1. Filtruj po "Font" lub "Woff2"
  2. Sprawdź czas pobierania, rozmiar plików i priorytet ładowania

3. WebPageTest

Narzędzie WebPageTest (webpagetest.org) oferuje szczegółową analizę ładowania zasobów, w tym czcionek, na różnych urządzeniach i łączach internetowych.

Najlepsze praktyki

  1. Używaj komponentu Font z Next.js 15 zamiast importowania czcionek bezpośrednio z Google Fonts lub innych zewnętrznych źródeł
  2. Ogranicz liczbę używanych fontów do 2-3 rodzin (np. jedna dla nagłówków, jedna dla tekstu, ewentualnie jedna monospacjowa)
  3. Wybieraj tylko potrzebne wagi i style - każda dodatkowa waga to dodatkowe dane do pobrania
  4. Używaj subsets specyficznych dla języków Twojej aplikacji
  5. Rozważ czcionki zmienne dla rodzin z wieloma wagami
  6. Dobieraj odpowiednią strategię wyświetlania (
    display
    ) w zależności od przeznaczenia czcionki
  7. Używaj zmiennych CSS dla większej elastyczności w stylizacji
  8. Testuj wydajność na różnych urządzeniach i połączeniach

Przykład kompleksowej implementacji

Poniż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}

Typowe problemy i ich rozwiązania

Problem 1: FOUT (Flash of Unstyled Text)

Rozwiązanie: Używaj

display: 'swap'
i upewnij się, że czcionki są preładowane.

Problem 2: Duży rozmiar plików czcionek

Rozwiązanie:

  • Ograniczaj liczbę wag i stylów
  • Używaj tylko niezbędnych subsetów
  • Rozważ czcionki zmienne
  • Optymalizuj pliki WOFF2 (Next.js robi to automatycznie)

Problem 3: Czcionki opóźniają ładowanie strony

Rozwiązanie:

  • Dla mniej krytycznych czcionek używaj
    display: 'optional'
  • Dla czcionek używanych tylko w określonych komponentach wyłącz preładowanie

Problem 4: Różnice w wyglądzie czcionek fallbackowych i niestandardowych

Rozwiązanie: Next.js 15 automatycznie dopasowuje metryki czcionek fallbackowych do docelowych czcionek, minimalizując przesunięcia. Możesz też użyć

adjustFontFallback: true
.

Kompatybilność z przeglądarkami

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.

Wsparcie dla języków z niestandardowymi systemami pisma

Przy pracy z aplikacjami wielojęzycznymi, pamiętaj o:

  1. Odpowiednich subsetach dla każdego języka
  2. Większych rozmiarach plików dla języków z rozbudowanymi systemami znaków (np. chiński, japoński)
  3. Wydajności renderingu - niektóre złożone systemy pisma mogą obciążać przeglądarkę
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});

Podsumowanie

Komponent Font w Next.js 15 zapewnia kompleksowe rozwiązanie dla zarządzania i optymalizacji czcionek w aplikacjach webowych:

  1. Automatyczne samohostowanie - eliminuje zewnętrzne żądania HTTP
  2. Zoptymalizowany format WOFF2 - mniejsze pliki do pobrania
  3. Zapobieganie layout shift - lepsze CLS
  4. Strategie wyświetlania - kontrola nad sposobem ładowania czcionek
  5. Preload i lazy loading - optymalne wykorzystanie przepustowości sieci

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.

Vai a CodeWorlds