Wstęp#

Przez ostatnie tygodnie przeszedłem przez kompleksową optymalizację mojej strony. Zaczęło się od prostej ciekawości czy da się wycisnąć więcej z tego generatora stron statycznych — a skończyło na serii eksperymentów, które dały wymierne rezultaty.

Punkt wyjścia był naprawdę przyzwoity. Hugo z definicji generuje szybkie strony. Mój wynik w Lighthouse oscylował gdzieś między 92 a 94 punktami. Ale kiedy zagłębiłem się w Chrome DevTools i zerknąłem na szczegóły transferu sieciowego, zobaczyłem rzeczy, których gołym okiem nie widać. Prawie 15 megabajtów transferu na stronę główną. 15 osobnych plików CSS, które generowały półtorej sekundy opóźnienia. Zero strategii cache’owania dla osób, które wracają na stronę.

Po wdrożeniu wszystkich zmian sytuacja wygląda zupełnie inaczej. Transfer spadł do około 350 kilobajtów. Lighthouse pokazuje stabilne 96-98 punktów. Powracający użytkownicy ładują stronę praktycznie natychmiastowo, bo agresywne cache’owanie robi swoją robotę.

W tym wpisie przejdę przez całą tę podróż. Pokażę każdy obszar optymalizacji, od obrazów, przez style i skrypty, po konfigurację serwera. Zobaczcie, które zmiany przyniosły największy efekt i dlaczego kolejność wdrażania ma znaczenie.

Anatomia problemu#

Zanim przejdziemy do rozwiązań, warto zrozumieć skalę wyzwania. Analiza pliku HAR z Chrome DevTools pokazała mi pełen obraz sytuacji.

Co ujawniła diagnostyka#

Strona wykonywała 34 zapytania HTTP przy pierwszym ładowaniu. Łączny transfer przekraczał 15 megabajtów. Rozkład był mocno nierównomierny i właśnie w tej nierównomierności kryła się mapa do optymalizacji.

Obrazy odpowiadały za 96% całego transferu. Trzy grafiki formacie PNG ważyły łącznie prawie 15 megabajtów. Same w sobie generowały ponad 4 sekundy czasu ładowania. Style CSS zajmowały zaledwie 29 kilobajtów, ale były rozrzucone po 15 osobnych plikach. Każdy request to około 100 milisekund narzutu. Sumarycznie dawało to półtorej sekundy na same style. JavaScript z Google Analytics i Tag Managera dokładał kolejne 736 kilobajtów.

Hierarchia wpływu#

Ta analiza od razu wskazała priorytety. Nie ma sensu optymalizować CSS, jeśli obrazy zjadają 96% transferu. Kolejność działań musiała odzwierciedlać rzeczywisty wpływ na wydajność.

Dlatego zacząłem od obrazów, potem przeszedłem do bundlingu CSS, następnie do JavaScriptu, a na końcu zajęłem się konfiguracją serwera HTTP. Ta sekwencja nie jest przypadkowa — każdy kolejny krok buduje na poprzednim i daje coraz większą kontrolę nad całością.

Obrazy największy game changer#

Optymalizacja obrazów to bez wątpienia najważniejsza pojedyncza zmiana, jaką można wprowadzić w projekcie Hugo. W moim przypadku przyniosła redukcję transferu o około 97%.

Problem w liczbach#

Trzy grafiki w formacie PNG ważyły łącznie prawie 15 megabajtów. Format PNG, choć świetny dla prostej grafiki, jest wysoce nieefektywny dla zdjęć i screenshotów z gradientami. Do tego obrazy były serwowane w pełnej rozdzielczości 4K, mimo że kontener na stronie nigdy nie przekraczał 1200 pikseli szerokości.

Rozwiązanie#

Wykorzystałem wbudowany w Hugo mechanizm przetwarzania obrazów. Stworzyłem shortcode, który automatycznie konwertuje obrazy do formatu WebP, generuje responsywne wersje dla różnych rozmiarów ekranów i dodaje leniwe ładowanie.

Kluczowe elementy rozwiązania to konwersja do WebP z jakością 80%, generowanie wariantów 800px, 1200px i 1600px oraz atrybut loading=“lazy” dla obrazów poza pierwszym ekranem.

Efekt#

Transfer obrazów spadł z 15 megabajtów do około 350 kilobajtów. Czas ładowania skrócił się z ponad 4 sekund do ułamka sekundy. Co najważniejsze, wizualna jakość pozostała praktycznie niezmieniona — format WebP oferuje o 25-34% mniejsze pliki niż JPEG przy tej samej jakości wizualnej.

Szczegółową implementację, w tym pełny kod shortcode i partial dla okładek, opisałem w dedykowanym wpisie: Hugo Image Processing: Jak odchudziłem stronę o 98%.

CSS – od 15 plików do jednego#

Po uporaniu się z obrazami, następnym krokiem było uporządkowanie warstwy stylów. Problem nie leżał w rozmiarze plików, ale w ich fragmentacji.

Problem w liczbach#

15 osobnych plików CSS generowało łącznie półtorej sekundy opóźnienia. Mimo że sumaryczny rozmiar wynosił zaledwie 29 kilobajtów po kompresji GZIP, każde zapytanie HTTP niosło ze sobą narzut czasowy rzędu 100 milisekund.

Rozwiązanie#

Hugo Pipes pozwala na bundlowanie wielu plików CSS w jeden. Wykorzystałem funkcje resources.Match do zebrania wszystkich stylów z motywu, resources.Concat do połączenia ich w jeden plik, minify do kompresji i fingerprint do dodania unikalnego hasha w nazwie.

Dodatkowo wdrożyłem preload hint, który informuje przeglądarkę o priorytecie ładowania głównego arkusza stylów.

Efekt#

Liczba zapytań o style spadła z 15 do 3. Czas ładowania CSS skrócił się z półtorej sekundy do 135 milisekund — redukcja o 92%.

Pełną implementację wraz z rozwiązywaniem typowych problemów znajdziecie w: Optymalizacja JS w Hugo: Bundling i Fingerprinting.

JavaScript – fingerprinting i bezpieczeństwo cache#

Warstwa JavaScript była stosunkowo lekka, ale miała krytyczny problem architektoniczny. Pliki miały ustawiony roczny cache, ale brakowało im fingerprintingu. To oznaczało, że po wdrożeniu poprawek użytkownicy mogli przez rok korzystać ze starego kodu.

Problem w liczbach#

Dwa pliki JavaScript o łącznej wadze niespełna 3 kilobajtów nie stanowiły problemu wydajnościowego. Problemem było ryzyko serwowania nieaktualnego kodu z cache przeglądarki.

Rozwiązanie#

Połączyłem oba pliki w jeden bundle z wykorzystaniem Hugo Pipes. Dodałem fingerprinting, który zmienia nazwę pliku przy każdej modyfikacji kodu. Wdrożyłem również Subresource Integrity — czyli atrybut integrity — który pozwala przeglądarce zweryfikować, że pobrany plik nie został podmieniony.

Efekt#

Liczba zapytań spadła o połowę. Transfer zmniejszył się o 36%. Co najważniejsze, cache stał się bezpieczny — każda zmiana w kodzie automatycznie invaliduje starą wersję w przeglądarkach użytkowników.

Szczegóły implementacji opisałem w: Optymalizacja JS w Hugo: Bundling i Fingerprinting.

Serwer HTTP – GZIP i cache headers#

Ostatnim elementem układanki była konfiguracja samego serwera. Nawet najlepiej zoptymalizowane pliki mogą być wolno dostarczane bez właściwych nagłówków HTTP.

Dwa filary optymalizacji serwera#

Pierwszy filar to kompresja GZIP. Algorytm pozwala zmniejszyć rozmiar plików tekstowych o 60-70% bez żadnej straty jakości. W moim przypadku plik CSS schudł z 25 do 5 kilobajtów.

Drugi filar to nagłówki cache. Dla plików z fingerprintem — takich jak zbundlowany CSS czy JavaScript — ustawiłem cache na rok z dyrektywą immutable. Dla HTML-a cache wynosi godzinę z wymuszeniem rewalidacji. Ta strategia sprawia, że powracający użytkownicy ładują zasoby statyczne w zero milisekund, bezpośrednio z dysku.

Efekt#

Pierwsza wizyta korzysta z GZIP i ładuje stronę znacznie szybciej. Każda kolejna wizyta jest praktycznie natychmiastowa, bo przeglądarka w ogóle nie łączy się z serwerem po style i skrypty. Pełną konfigurację .htaccess dla Apache oraz instrukcje weryfikacji znajdziecie w: Optymalizacja serwera HTTP dla Hugo: GZIP i cache headers.

Konfiguracja minifikacji#

Poza bundlingiem i cache’owaniem warto też dostroić sam proces minifikacji w Hugo. Domyślne ustawienia są konserwatywne. Bardziej agresywna konfiguracja pozwala wycisnąć dodatkowe kilkanaście procent z rozmiaru plików.

W pliku hugo.toml można ustawić usuwanie komentarzy i zbędnych białych znaków z HTML-a, zaokrąglanie wartości liczbowych w CSS do dwóch miejsc po przecinku oraz skracanie nazw zmiennych w JavaScript. Te zmiany w połączeniu z flagami –minify i –gc podczas budowania dają optymalny wynik bez ryzyka uszkodzenia kodu.

Podsumowanie wyników#

Liczby nie kłamią. Poniższe zestawienie pokazuje, jak poszczególne zmiany przełożyły się na metryki wydajnościowe.

MetrykaStan początkowyPo optymalizacjiZmiana
Transfer całkowity~15 MB~350 KB↓ 97%
Requesty HTTP3414↓ 60%
Czas ładowania CSS1500 ms135 ms↓ 91%
Lighthouse (Mobile)94/10097/100↑ 3 pkt
Total Blocking TimeWysokiZredukowany↓ 49%
Strategia CacheBrak / Losowaimmutable (1 rok)Bezpieczny cache

Szczególnie warta uwagi jest redukcja czasu ładowania stylów. Mimo że ich waga (w kilobajtach) zmieniła się nieznacznie, to drastyczne zmniejszenie liczby zapytań (RTT) sprawiło, że przeglądarka renderuje stronę niemal natychmiastowo. Dla użytkownika końcowego oznacza to jedno: podczas powrotu na stronę interfejs ładuje się w “zero milisekund”, bezpośrednio z dysku urządzenia.

Wnioski i rekomendacje#

Po przejściu przez całą ścieżkę optymalizacji mam kilka obserwacji, które mogą być przydatne dla innych.

Zacznij od obrazów#

To niemal zawsze największy problem. Dopiero potem zajmij się bundlingiem CSS i JS. Na końcu skonfiguruj serwer. Ta sekwencja maksymalizuje efekt przy minimalnym nakładzie pracy.

Mierz, nie zgaduj#

Chrome DevTools i pliki HAR to twoi przyjaciele. Bez twardych danych łatwo stracić czas na optymalizacje, które nie mają realnego wpływu. Lighthouse pokazuje wynik ogólny, ale to analiza sieci ujawnia prawdziwe wąskie gardła.

Fingerprinting jest obowiązkowy#

Jeśli chcesz agresywnie cache’ować zasoby, musisz mieć fingerprinting. Bez niego ryzykujesz serwowanie nieaktualnego kodu użytkownikom. Hugo obsługuje to natywnie przez funkcję fingerprint w Hugo Pipes.

Proste rozwiązania działają#

Żadna z opisanych optymalizacji nie wymagała zewnętrznych narzędzi czy skomplikowanej infrastruktury. Wszystko opiera się na wbudowanych mechanizmach Hugo i standardowej konfiguracji serwera. To sprawia, że rozwiązania są łatwe do utrzymania i powtórzenia w innych projektach.

Twoja kolej#

Optymalizacja to nie jednorazowy “strzał”, ale proces. Mój wynik 98/100 w Lighthouse cieszy oko, ale technologia idzie do przodu. Za pół roku standardy Web Vitals mogą się zmienić, a Chrome wprowadzi nowe mechanizmy kompresji.

Dlatego zamiast zostawiać Cię z listą linków, mam propozycję: zacznij od audytu. Otwórz teraz DevTools na swojej stronie, wejdź w zakładkę Network i odśwież z wyczyszczonym cachem. Zobacz, ile ważą Twoje obrazki i czy nie ładujesz jQuery tylko dla jednego slidera.

Powodzenia w walce o każdy kilobajt!