Site-ul tău descarcă 83 KB de font ca să scrie „ă”
Cum am tăiat 89% din fonturile latin-ext pe blackbit.ro, de ce unicode-range e o capcană, și ce înseamnă asta pentru orice site în română.
Dacă site-ul tău e în română, browserul vizitatorului descarcă un fișier de font suplimentar la fiecare primă vizită. Nu pentru că designerul a cerut ceva exotic, ci pentru un singur motiv: cuvântul „această" conține un ă.
Am măsurat exact cât ne costa asta pe blackbit.ro și am redus fișierul cu 89%, fără să pierdem un singur caracter din ce afișăm. Mai jos e metoda, cifrele reale și capcana care transformă optimizarea într-un bug vizibil dacă o aplici superficial.
De ce apare un al doilea fișier de font
Google Fonts — și orice pipeline serios de fonturi web — împarte un font în subseturi pe intervale Unicode. Cele două care ne interesează:
- latin — literele de bază, semnele de punctuație, cifrele, plus accentele occidentale uzuale (é, ü, ñ). Aici intră și â și î, fiindcă sunt în Latin-1.
- latin-ext — tot restul Europei Centrale și de Est, alfabetul fonetic internațional, o colecție de simboluri valutare.
Fiecare subset e declarat în CSS cu un unicode-range. Browserul descarcă fișierul doar dacă întâlnește pe pagină un caracter din intervalul respectiv. E un mecanism bun: un site în engleză nu plătește niciodată pentru latin-ext.
Un site în română plătește întotdeauna. Litera ă (U+0103), ș (U+0219) și ț (U+021B) sunt toate în latin-ext. Nu există text românesc corect scris care să le evite.
Cât foloseam de fapt
Înainte de a tăia ceva, am numărat. Am descărcat toate paginile publice ale site-ului și am extras fiecare caracter care cade în intervalul latin-ext, excluzând conținutul din <script> și <style>.
Rezultatul, pe opt pagini de conținut:
| Caracter | Cod | Apariții |
|---|---|---|
| ă | U+0103 | 1.557 |
| ț | U+021B | 808 |
| ș | U+0219 | 484 |
| Ș | U+0218 | 2 |
Patru caractere. Fișierul livrat conținea 733 de glife.
Cu alte cuvinte: 99,5% din fișier era greutate moartă, descărcată de fiecare vizitator, o dată pentru fontul normal și încă o dată pentru cel italic dacă pagina folosea italice.
Detaliul românesc pe care îl ratează majoritatea
Aici merită oprit puțin, fiindcă e o sursă constantă de text stricat pe site-urile românești.
Literele ș și ț se scriu corect cu virgulă dedesubt: U+0219 și U+021B. Există însă și variantele cu sedilă — U+015F și U+0163 — moștenite din codificări vechi și din vremea când Unicode nu le separase încă. Vizual seamănă. Semantic sunt caractere diferite.
Conținutul migrat dintr-un CMS vechi, o bază de date importată, un document Word din 2009 — toate pot conține variantele cu sedilă. Dacă subsetezi fontul doar pe formele corecte, textul vechi rămâne fără glife.
De aceea subsetul nostru păstrează ambele forme, plus diacriticele central-europene și turcești. Costă sub un kilobyte în total și scutește un bug pe care îl descoperi mult prea târziu, într-un articol vechi pe care nu-l mai citește nimeni.
Cum se face, concret
Instrumentul e pyftsubset, din pachetul fonttools. Primește fontul, lista de coduri Unicode de păstrat și formatul de ieșire:
pyftsubset inter-latin-ext.woff2 \
--unicodes=U+0102,U+0103,U+0218,U+0219,U+021A,U+021B,U+015E,U+015F,U+0162,U+0163 \
--layout-features='*' \
--flavor=woff2 \
--output-file=inter-latin-ext-v2.woff2
Două lucruri de urmărit dacă fontul e variabil, cum e Inter:
- verifică după subsetare că tabela
fvara supraviețuit — altfel pierzi întreg intervalul de grosimi și rămâi cu o singură greutate; - păstrează
--layout-features='*', altfel pierzi kerningul și perechile de litere se lipesc urât.
Și versionează numele fișierului. Un @font-face nu acceptă query string, deci un ?v=2 nu invalidează nimic — fișierul rămâne în cache-ul CDN-ului cu conținutul vechi.
Capcana: unicode-range nu e o sugestie
Aici se strică majoritatea încercărilor de subsetare, și merită enunțat răspicat:
Un caracter care rămâne declarat în unicode-range, dar a fost scos din font, nu cade pe fontul de sistem. Se randează ca pătrat gol.
Logica browserului e simplă: unicode-range e o promisiune. Browserul o crede, alege fișierul pentru acel caracter, nu găsește glifa și desenează .notdef. Nu se întoarce la fontul următor din stivă, pentru că din punctul lui de vedere fontul potrivit a fost deja ales.
Concluzia practică: după ce subsetezi, îngustează unicode-range exact pe glifele păstrate. Dacă mai târziu lărgești subsetul, lărgește și intervalul. Și invers. Cele două trebuie să se miște împreună, mereu.
Rezultatul măsurat
| Fișier | Înainte | După |
|---|---|---|
| Inter normal, latin-ext | 83,1 KB | 9,5 KB |
| Inter italic, latin-ext | 36,7 KB | 4,2 KB |
| JetBrains Mono, latin-ext | 11,4 KB | 4,9 KB |
Pentru un vizitator nou, transferul total de fonturi a scăzut de la 131 KB la 57,5 KB. Fontul normal latin-ext era și preîncărcat, deci stătea direct în calea critică de randare — acolo unde fiecare kilobyte se vede în timpul până la primul text afișat.
Verificarea finală n-a fost un raport, ci o captură de ecran: pagina încărcată real, cu diacriticele la locul lor. O optimizare de fonturi care nu se termină cu o verificare vizuală e o optimizare pe care n-ai făcut-o.
Ce poți verifica pe site-ul tău în cinci minute
- Deschide DevTools → Network → filtrează după Font și reîncarcă. Numără fișierele și octeții.
- Dacă vezi un fișier
latin-ext, uită-te la mărimea lui. Peste 30 KB înseamnă că livrezi Europa Centrală ca să scrii „și". - Caută în CSS după
unicode-range. Dacă intervalul e cel implicit de la Google și fișierul e subsetat de cineva înaintea ta, ai deja pătrate goale undeva în site. - Verifică dacă fonturile sunt găzduite local. Dacă browserul mai cere ceva de la
fonts.gstatic.com, ai și o dependență externă în calea critică, și o discuție de purtat despre GDPR.
Niciunul dintre pașii ăștia nu cere un instrument plătit sau o rescriere. Cer doar să te uiți la ce trimiți efectiv pe fir.
Optimizarea fonturilor nu e o chestiune de instrumente, ci de măsurat ce trimiți efectiv. Patru caractere nu justifică 83 KB — dar nici subsetarea nu se face pe ghicite: unicode-range și conținutul fontului trebuie să rămână sincronizate, altfel bugul apare exact acolo unde nu te uiți.