Przejdź do treści

~/www cat fonty-na-stronie-woff2-font-disp….md

Fonty na stronie a szybkość: WOFF2, font-display, preload i polskie znaki

Jak ładować fonty na stronie bez spowalniania: WOFF2, @font-face, font-display swap, preload, podzbiór z polskimi znakami i Google Fonts lokalnie.

CZCzarek Zawolski--aktualizacja=--czas=13 min--dział=WWW i WordPress
Ilustracja do artykułu „Fonty na stronie a szybkość: WOFF2, font-display, preload i polskie znaki” — okno edytora z ikoną działu WWW i WordPress
tldr.txt — W skrócie

~ xad tldr fonty-na-stronie-woff2-fon…

  • Na stronę wystarczy dziś tylko WOFF2 — obsługują go wszystkie aktualne przeglądarki, a kompresja Brotli daje pliki ok. 30% mniejsze niż WOFF.
  • font-display: swap pokazuje tekst od razu fontem zapasowym; optional eliminuje skok układu (CLS), ale przy wolnym łączu font może się nie pojawić.
  • Preloaduj tylko jeden, najważniejszy plik WOFF2 i zawsze z atrybutem crossorigin — bez niego font pobierze się dwa razy.
  • Polskie litery ą ę ł ś ż ź ć ń są w podzbiorze latin-ext, a ó w latin — przy własnym subsettingu dodaj zakresy U+0104-0107, U+0118-0119, U+0141-0144, U+015A-015B i U+0179-017C.
  • Hostuj fonty u siebie: od 2020 r. przeglądarki nie współdzielą cache między domenami, a ładowanie Google Fonts z serwerów Google bez zgody sąd w Monachium uznał w 2022 r. za naruszenie RODO.
$ tree --spis-tresci

Fonty na stronie spowalniają ją wtedy, gdy są za duże, ładowane za późno albo z cudzego serwera. Żeby nie psuły wyniku w PageSpeed Insights, serwuj tylko pliki WOFF2, ustaw w @font-face właściwość font-display (zwykle swap lub optional), preloaduj jeden najważniejszy plik z atrybutem crossorigin i przytnij font do potrzebnych znaków — pamiętając, że polskie litery leżą w innym podzbiorze niż podstawowa łacinka.

Poniżej znajdziesz działające przykłady CSS, HTML i poleceń oraz odpowiedź, czy Google Fonts ładować z serwerów Google, czy lokalnie.

Jak fonty wpływają na LCP, CLS i wynik w PageSpeed

Plik fontu jest pobierany dopiero wtedy, gdy przeglądarka przetworzy HTML i CSS i stwierdzi, że widoczny tekst go używa. Do tego czasu ma dwa wyjścia:

  • ukryć tekst i czekać na font (FOIT, flash of invisible text) — jeśli największym elementem na ekranie jest nagłówek, opóźnia to LCP,
  • pokazać tekst fontem zapasowym i podmienić go po pobraniu (FOUT, flash of unstyled text) — tekst jest od razu czytelny, ale inne wymiary liter przesuwają układ, co podbija CLS.

Optymalizacja sprowadza się więc do trzech rzeczy: mniej bajtów, wcześniejsze pobranie i kontrolowana podmiana fontu. Progi Core Web Vitals i sposób ich mierzenia opisuje poradnik jak przetestować stronę internetową.

Formaty fontów: dlaczego dziś wystarczy WOFF2

FormatCo to jestNa stronie
TTF (TrueType)klasyczny plik fontu systemowegonie — bez kompresji, duży
OTF (OpenType)plik systemowy, często z krzywymi CFFnie — jak wyżej
WOFF„opakowanie” TTF/OTF z kompresją zlibzbędny, chyba że musisz wspierać bardzo stare przeglądarki
WOFF2opakowanie z kompresją Brotli i przekształceniem tabel fontutak, jedyny potrzebny
EOT, SVGdawne formaty dla Internet Explorera i starego iOSnie

WOFF2 jest rekomendacją W3C od 2018 roku i obsługują go wszystkie aktualne przeglądarki. Według web.dev kompresuje około 30% lepiej niż WOFF. Długie listy src z formatami eot, ttf i svg w starych motywach to relikt. TTF lub OTF zostaw jako plik źródłowy do subsettingu i konwersji.

@font-face krok po kroku: przykład z WOFF2

Minimalna, poprawna deklaracja dla fontu w dwóch grubościach wygląda tak:

@font-face {
  font-family: "Lato";
  src: url("/fonts/lato-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Lato";
  src: url("/fonts/lato-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

body {
  font-family: "Lato", Arial, sans-serif;
}

Kilka zasad, które oszczędzają kłopotów:

  1. Ta sama nazwa w font-family dla wszystkich grubości i stylów — przeglądarka sama dobierze plik po font-weight i font-style. Nazwy w stylu "Lato Bold" psują dziedziczenie i powodują sztuczne pogrubianie.
  2. Ładuj tylko te grubości, których naprawdę używasz. Każda to osobny plik.
  3. Deklaracje @font-face umieść w CSS ładowanym w <head> (najlepiej w krytycznym CSS osadzonym w HTML), a nie w pliku dociąganym przez @import.
  4. Serwer powinien wysyłać pliki z nagłówkiem Content-Type: font/woff2 i długim Cache-Control (np. max-age=31536000, immutable) przy wersjonowanej nazwie pliku. Fonty dobrze buforuje też sieć CDN.

Pełny opis deskryptorów znajdziesz w dokumentacji MDN o font-display i @font-face.

font-display: swap, optional czy fallback?

Właściwość font-display określa, jak długo przeglądarka ukrywa tekst (okres blokowania) i jak długo jeszcze może podmienić font zapasowy na docelowy (okres zamiany). Orientacyjne czasy podaje poradnik web.dev o dobrych praktykach dla fontów:

WartośćBlokowanieZamianaEfekt
blockok. 2–3 sbez limituniewidoczny tekst, gorszy LCP; tylko dla fontów ikon
swapok. 0 msbez limitutekst od razu, później podmiana i możliwy skok układu
fallbackok. 100 msok. 3 skompromis: spóźniony font nie zostanie już podmieniony
optionalok. 100 msbrakbrak skoku układu; przy wolnym łączu zostaje font zapasowy
autozależy od przeglądarkizależy od przeglądarkiw praktyce zwykle zbliżone do block

Jak wybrać:

  • swap — domyślny wybór dla tekstu, zwłaszcza gdy font jest częścią identyfikacji marki. Żeby nie płacić za niego wyższym CLS, dopasuj font zapasowy (o tym niżej).
  • optional — najlepszy dla CLS. Na wolnym łączu pierwsza wizyta pokaże font systemowy, kolejna — docelowy z cache. Dobrze łączy się z preloadem.
  • fallback — rozsądny środek, gdy nie chcesz późnej podmiany po kilku sekundach czytania.

W adresie Google Fonts odpowiada za to parametr &display=swap, który generator Google dodaje domyślnie.

Preload fontu: tylko jednego pliku i zawsze z crossorigin

Preload pozwala zacząć pobieranie fontu od razu, zamiast czekać na CSS:

<link rel="preload" href="/fonts/lato-400.woff2" as="font" type="font/woff2" crossorigin>

Atrybut crossorigin jest wymagany nawet dla fontu z Twojej własnej domeny. Fonty są zawsze pobierane w trybie CORS, więc preload bez tego atrybutu nie zostanie dopasowany do żądania z @font-face i plik pobierze się dwa razy (Chrome pokaże wtedy ostrzeżenie w konsoli).

Zasady:

  1. Preloaduj tylko font widoczny od razu na górze strony, i to jeden, góra dwa pliki — każdy konkuruje o łącze z obrazem LCP.
  2. Preload ignoruje unicode-range, więc pobierze wskazany plik nawet wtedy, gdy strona go nie użyje.
  3. Ścieżka w href musi być identyczna jak w @font-face, łącznie z parametrami wersji.

Jeżeli zostajesz przy Google Fonts z serwerów Google, preload pliku nie ma sensu (adresy plików się zmieniają). Zamiast tego dodaj preconnect:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

Polskie znaki w fontach na stronie: subsetting i unicode-range

Subsetting to wycięcie z fontu glifów, których nie używasz: cyrylicy, greki, znaków wietnamskich czy symboli. Trzeba przy tym uważać, żeby nie wyciąć polskich liter.

Jak dzieli fonty Google Fonts: latin i latin-ext

Google Fonts dzieli kroje na podzbiory z własnym unicode-range, a przeglądarka pobiera tylko te, których znaki występują na stronie. Dla łacinki są dwa:

  • latin — zakres U+0000-00FF (ASCII i Latin-1) plus typograficzne znaki z U+2000-206F, m.in. polskie cudzysłowy „ ” i półpauza. Tu jest ó i Ó (U+00F3, U+00D3).
  • latin-ext — m.in. zakres U+0100-02BA, w którym leżą ą ę ł ś ż ź ć ń i ich wielkie odpowiedniki.

Efekt: polska strona pobiera z Google Fonts dwa pliki na każdą grubość, bo prawie każdy akapit zawiera ą lub ł. A kto przy samodzielnym hostingu skopiował tylko plik latin, dostaje ó w docelowym kroju, a ą i ł — w foncie zapasowym. Jak szybko sprawdzić, czy dany krój w ogóle ma polskie litery, opisuje poradnik jak sprawdzić, czy czcionka ma polskie znaki.

Własny podzbiór „latin + polskie znaki”

Dla polskiej strony najlepiej zrobić jeden plik na grubość, zawierający Latin-1, polskie litery i interpunkcję. Polskie znaki spoza Latin-1 to dokładnie te zakresy:

LiteryZakres Unicode
Ą ą Ć ćU+0104-0107
Ę ęU+0118-0119
Ł ł Ń ńU+0141-0144
Ś śU+015A-015B
Ź ź Ż żU+0179-017C
Ó óU+00D3, U+00F3 (w Latin-1)

Podzbiór zrobisz narzędziem pyftsubset z pakietu fontTools. Do zapisu WOFF2 potrzebny jest też moduł brotli:

pip install fonttools brotli

pyftsubset Lato-Regular.ttf \
  --unicodes="U+0000-00FF,U+0104-0107,U+0118-0119,U+0141-0144,U+015A-015B,U+0179-017C,U+2000-206F,U+20AC,U+2122" \
  --layout-features='*' \
  --flavor=woff2 \
  --output-file=lato-400-pl.woff2

Opcja --layout-features='*' zachowuje wszystkie funkcje OpenType (np. cyfry tabelaryczne, alternatywne glify). Bez niej fontTools zostawia zestaw domyślny z kerningiem i podstawowymi ligaturami, a plik jest mniejszy.

Dla skali: zmienny Inter z repozytorium Google Fonts (osie opsz i wght) waży jako TTF ok. 857 KB, po samej konwersji do WOFF2 ok. 331 KB, a po przycięciu powyższymi zakresami z domyślnym zestawem funkcji OpenType — ok. 65 KB. Wszystkie polskie litery, cudzysłowy „” i półpauza zostają.

W CSS warto dopisać pasujący unicode-range, żeby przeglądarka wiedziała, które znaki ten plik obsługuje:

@font-face {
  font-family: "Lato";
  src: url("/fonts/lato-400-pl.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0104-0107, U+0118-0119, U+0141-0144,
                 U+015A-015B, U+0179-017C, U+2000-206F, U+20AC, U+2122;
}

Uwaga: Przed subsettingiem sprawdź licencję fontu. Kroje z Google Fonts są w większości na licencji SIL Open Font License, która pozwala na modyfikację i samodzielny hosting. Fonty komercyjne często mają w licencji osobne zasady dla wersji webowej.

glyphhanger: podzbiór na podstawie treści strony

glyphhanger (autorstwa Zacha Leathermana) to narzędzie w Node.js, które odwiedza stronę, zbiera użyte znaki i wywołuje pyftsubset za Ciebie. Wymaga zainstalowanych wcześniej fontTools i brotli:

npm install -g glyphhanger

# jakie znaki występują na stronie (i do 10 podstron)
glyphhanger https://twojadomena.pl --spider

# podzbiór Latin z dopisanymi polskimi literami
glyphhanger --LATIN --whitelist="ąćęłńóśźżĄĆĘŁŃÓŚŹŻ„”–" --formats=woff2 --subset="Lato-*.ttf"

Przycinanie dokładnie do znaków z dzisiejszej treści jest ryzykowne: nowy artykuł z literą spoza listy wyświetli ją fontem zapasowym. Na blogu czy w sklepie bezpieczniej trzymać się zakresu „Latin-1 + polskie znaki”.

Konwersja TTF do WOFF2

Jeśli nie potrzebujesz podzbioru, wystarczy sama konwersja:

  • pyftsubset z --unicodes="*" i --flavor=woff2 — konwersja bez wycinania znaków,
  • woff2_compress z oficjalnego repozytorium Google google/woff2 (w Debianie i Ubuntu pakiet woff2):
sudo apt install woff2
woff2_compress Lato-Regular.ttf   # tworzy Lato-Regular.woff2 obok

Fonty zmienne (variable fonts): jeden plik zamiast kilku grubości

Font zmienny zawiera w jednym pliku całą oś grubości (a czasem też szerokości, pochylenia czy rozmiaru optycznego). Deklarujesz go zakresem:

@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-var-pl.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

h1 { font-weight: 750; }

Opłaca się przy trzech lub więcej grubościach — jeden plik zmienny jest zwykle mniejszy niż suma statycznych. Przy jednej–dwóch grubościach statyczne bywają lżejsze. Google Fonts serwuje kroje zmienne automatycznie: w arkuszu dla Inter z grubościami 400 i 700 obie reguły @font-face wskazują ten sam plik WOFF2.

Subsetting działa także na fontach zmiennych — pyftsubset zachowuje osie.

Google Fonts lokalnie czy z serwerów Google?

W 2026 roku praktycznie zawsze lepiej hostować fonty u siebie. Powody są trzy.

Prywatność i RODO: wyrok z Monachium

20 stycznia 2022 r. Sąd Krajowy w Monachium (LG München I, sygn. 3 O 17493/20) uznał, że osadzenie Google Fonts ładowanych z serwerów Google bez zgody użytkownika narusza RODO, bo przeglądarka przekazuje Google adres IP odwiedzającego. Sąd zasądził od właściciela strony 100 euro zadośćuczynienia i wskazał, że nie ma tu uzasadnionego interesu, skoro fonty można hostować lokalnie. To wyrok niemieckiego sądu pierwszej instancji, niewiążący w Polsce, ale po nim w Niemczech posypały się masowe wezwania do zapłaty, a lokalny hosting stał się standardem.

Wydajność: koniec współdzielonego cache

Dawny argument „użytkownik ma już Roboto z Google w cache” nie działa od lat. Chrome od wersji 86 (2020), Firefox od 85, a Safari jeszcze wcześniej dzielą cache HTTP według odwiedzanej witryny — ten sam plik z fonts.gstatic.com pobrany na innej stronie jest dla Twojej strony pobierany od nowa.

Z serwerami Google dochodzą dwa dodatkowe połączenia (fonts.googleapis.com i fonts.gstatic.com) i blokujący renderowanie arkusz CSS. U siebie fonty idą tym samym połączeniem HTTP/2 co reszta strony i możesz je preloadować. Web.dev zaznacza jednak, że self-hosting nie jest szybszy automatycznie — na wolnym hostingu bez HTTP/2 może przegrać z Google.

Jak przenieść Google Fonts na swój serwer

  1. Pobierz arkusz CSS z nowoczesnym User-Agentem — Google zwraca wtedy pliki WOFF2 z gotowymi unicode-range:
UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0 Safari/537.36"
curl -A "$UA" "https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap" -o lato.css
grep -o "https://fonts.gstatic.com[^)]*" lato.css   # adresy plików WOFF2
  1. Pobierz pliki z podzbiorów latin i latin-ext (inne, np. cyrillic, możesz pominąć), skopiuj ich bloki @font-face do swojego CSS i zamień adresy na lokalne.
  2. Albo zrób własny podzbiór „latin + PL” z pliku TTF, jak wyżej — wychodzi jeden plik na grubość zamiast dwóch.
  3. Usuń z szablonu <link> do fonts.googleapis.com i ewentualne @import.

Jeśli wolisz gotowy zestaw bez wiersza poleceń, generator webfontów z gotowym CSS @font-face (ang.) przerobi pliki TTF lub OTF w przeglądarce na komplet WOFF2 z arkuszem stylów (domyślnie z font-display: swap) i stroną podglądu. Polskie znaki sprawdź potem na podglądzie — narzędzie konwertuje cały font, więc liczy się to, co ma plik źródłowy.

Font zapasowy z size-adjust i ascent-override: mniej CLS

Przy swap skok układu bierze się z różnicy wymiarów fontu zapasowego i docelowego: zmienia się łamanie wierszy i wysokość linii. Zniwelujesz to, tworząc dopasowany font zapasowy z lokalnego Ariala:

@font-face {
  font-family: "Inter Fallback";
  src: local("Arial");
  size-adjust: 109%;
  ascent-override: 89%;
  descent-override: 22%;
  line-gap-override: 0%;
}

body {
  font-family: "Inter", "Inter Fallback", sans-serif;
}

Skąd te liczby (przybliżone, dla pary Inter → Arial):

  • size-adjust to stosunek średniej szerokości znaków obu fontów — tu ok. 0,527 em dla Intera do ok. 0,482 em dla Ariala,
  • ascent-override i descent-override to metryki fontu docelowego podzielone przez size-adjust (Inter: wydłużenia górne 1984 i dolne 494 jednostek przy 2048 jednostkach na em).

Dokładniejsze wartości policzą biblioteki ważące częstość liter (np. Capsize, Fontaine), a Next.js robi to automatycznie. Wynik sprawdź wzrokowo, przełączając w DevTools font docelowy na zapasowy.

Ważne: Według danych caniuse Safari (stan na 2026 r.) obsługuje size-adjust, ale ignoruje ascent-override i descent-override. Na iPhone’ach dopasowanie będzie więc tylko częściowe.

WordPress: motywy i wtyczki, które ładują Google Fonts

W WordPressie fonty dodają motyw, kreator stron i wtyczki — często każde osobno, więc strona potrafi ładować ten sam krój dwa razy.

  1. Otwórz stronę z DevTools (F12) → Network → filtr Font i odśwież. Sprawdź, czy są żądania do fonts.gstatic.com i ile plików się pobiera.
  2. W motywach blokowych (WordPress 6.5+) użyj wbudowanej Biblioteki fontów w edytorze witryny (Wygląd → Edytor → Style → Typografia). Zainstalowany z niej krój Google WordPress pobiera na Twój serwer, domyślnie do katalogu wp-content/uploads/fonts.
  3. W motywach klasycznych i kreatorach (Elementor, Divi i podobne) poszukaj w ustawieniach opcji lokalnego ładowania fontów lub wyłączenia Google Fonts — wiele popularnych motywów i kreatorów ją ma. Wybierając motyw, sprawdź to przed instalacją; zestawienie motywów WordPress pomoże zawęzić wybór.
  4. Gdy motyw nie daje takiej opcji, wtyczka OMGF (w katalogu wordpress.org) przechwytuje żądania do Google Fonts i serwuje kopie z Twojego serwera.

Ostrożnie z wtyczkami „optymalizacyjnymi”, które przepisują CSS. Gdy dwie naraz lokalizują fonty, łatwo o zgubiony podzbiór latin-ext — i polskie litery w innym kroju. Po każdej zmianie obejrzyj tekst z ą, ę i ł.

Jak zmierzyć efekt: PageSpeed Insights, DevTools i WebPageTest

Zmierz stan przed zmianami i po nich, w tych samych warunkach.

  • PageSpeed Insights — w Lighthouse 13 dawny audyt „Ensure text remains visible during webfont load” zastąpiła analiza Font display w sekcji Insights. Dane z terenu (CrUX) zmienią się dopiero po kilku tygodniach.
  • Chrome DevTools, zakładka Network, filtr Font — liczba plików, ich rozmiar, kolumna Initiator (kto zażądał fontu) i czy preloadowany plik nie pobiera się drugi raz.
  • Elements → Computed → sekcja Rendered Fonts na dole: zaznacz akapit z polskimi literami i sprawdź, czy wszystkie glify pochodzą z „Network resource”. Jeśli część jest z „Local file” (np. Arial), Twój podzbiór nie ma tych znaków.
  • Performance → nagranie ładowania strony → ścieżka Layout shifts pokaże, czy podmiana fontu przesuwa układ.
  • WebPageTest — wykres waterfall pokaże, kiedy fonty zaczynają się pobierać względem CSS, a widok filmstrip — moment podmiany tekstu. Szczegóły w poradniku jak czytać wykres waterfall w WebPageTest.

Lista kontrolna: fonty na stronie

  1. Tylko WOFF2, najwyżej 2–4 pliki na stronę (przy wielu grubościach font zmienny).
  2. Podzbiór: Latin-1, polskie litery i U+2000-206F.
  3. font-display: swap albo optional, gdy liczy się przede wszystkim CLS.
  4. Jeden preload z as="font", type="font/woff2" i crossorigin.
  5. Fonty na własnym serwerze z długim Cache-Control, bez żądań do fonts.googleapis.com.
  6. Font zapasowy dopasowany przez size-adjust i ascent-override.
  7. Kontrola w DevTools: Rendered Fonts na tekście z ą ę ł ś ż ź ć ń ó.

~ man faq

Najczęściej zadawane pytania

Jaki format fontu wybrać na stronę internetową?

WOFF2. Obsługują go wszystkie współczesne przeglądarki, a pliki są wyraźnie mniejsze niż TTF, OTF i WOFF. TTF lub OTF zostaw do instalacji w systemie i jako plik źródłowy do konwersji.

Co daje font-display: swap?

Przeglądarka od razu wyświetla tekst fontem zapasowym, a po pobraniu fontu webowego podmienia go. Tekst nie jest niewidoczny, ale podmiana może przesunąć układ strony, co zwiększa CLS.

Jak hostować Google Fonts lokalnie?

Pobierz pliki WOFF2 (np. z arkusza CSS Google Fonts albo repozytorium projektu), wrzuć je na swój serwer i zapisz własne reguły @font-face z font-display i unicode-range. Usuń z kodu strony linki do fonts.googleapis.com i sprawdź w DevTools, że nie ma już żądań do domen Google.

Dlaczego na stronie polskie znaki wyświetlają się inną czcionką?

Font lub jego podzbiór nie zawiera polskich liter, więc przeglądarka bierze je z fontu zapasowego. Najczęściej brakuje podzbioru latin-ext albo własny subset został zrobiony tylko z zakresu Latin-1.

Czy preload fontu przyspiesza stronę?

Tak, jeśli dotyczy fontu używanego w treści widocznej od razu, bo przeglądarka zaczyna pobieranie przed przetworzeniem CSS. Preload kilku plików naraz konkuruje jednak o łącze z obrazem LCP i CSS, więc ogranicz się do jednego lub dwóch.

Ten artykuł jest częścią tematu

CZ

$ whoami

Czarek Zawolski

Założyciel i redaktor XAD.pl. Pisze o sieciach, bezpieczeństwie IT, administracji systemami Windows i Linux oraz o sprzęcie, który sprawia ludziom problemy na co dzień.

~ ls ../podobne