Dlaczego Twoja strona powinna wysyłać mniej żądań, a nie tylko mniejsze
Malutkie pliki nie są darmowe. Współczesny HTTP zmniejszył narzut żądań, ale nie sprawił, że przestał mieć znaczenie.
Spis treści
- Wygodny mit „po prostu zmniejsz każdy plik”
- Żądania to nie tylko bajty
- „Ale HTTP/2 to naprawił” — w większości nie
- Prawda mieszka w waterfallu
- Mniejsze pliki nadal mają znaczenie — tylko nie wszystkie jednakowo
- Ukryte koszty wielu małych plików
- 1. Późne odkrywanie
- 2. Narzut nagłówków
- 3. Przerywanie głównego wątku
- 4. Złożoność cache
- Bundling wrócił, ale z rozsądkiem
- Żądania zewnętrzne zasługują na dodatkową podejrzliwość
- Praktyczna lista kontrolna redukcji żądań
- Usuń
- Łącz z namysłem
- Odrocz
- Cache’uj poprawnie
- Zmierz ponownie
- Jak wygląda dobry stan
Wygodny mit „po prostu zmniejsz każdy plik”
Przez lata porady dotyczące wydajności WWW brzmiały prosto: kompresuj wszystko, minifikuj wszystko, zmniejsz każdy zasób.
Ta rada nadal jest zasadniczo słuszna. Skrypt o rozmiarze 40 KB jest zwykle lepszy niż skrypt o rozmiarze 400 KB. Zoptymalizowany obraz jest lepszy niż surowy eksport. Brotli, AVIF, minifikacja CSS, tree shaking i tworzenie podzbiorów fontów mają znaczenie.
Jednak na wielu stronach produkcyjnych większym problemem nie jest już pojedynczy zbyt duży plik. Jest nim liczba rzeczy, o które przeglądarka musi poprosić, zanim strona zacznie sprawiać wrażenie użytecznej.
Strona może wyglądać zdyscyplinowanie pod względem rozmiarów plików, a mimo to być wolna, ponieważ wysyła 120 żądań: fragmenty CSS, chunki JavaScript, tagi zewnętrzne, pliki fontów, piksele śledzące, sprite’y ikon, endpointy JSON, preloads, beacony analityczne i rewalidacje cache. Każdy z nich może być „mały”. Razem tworzą długi, kruchy waterfall.
Praktyczna zasada jest taka: gdy pojedyncze zasoby są już rozsądnie skompresowane, zmniejszenie liczby żądań często poprawia doświadczenie użytkownika bardziej niż urwanie kilku kilobajtów z każdego pliku.
Żądania to nie tylko bajty
Żądanie sieciowe nie jest wyłącznie transferem danych. To sekwencja pracy.
Przeglądarka musi odkryć zasób, zdecydować, kiedy go pobrać, zaplanować go względem innych zasobów, wysłać nagłówki, poczekać na serwer, odebrać nagłówki, sparsować odpowiedź, często ją zdekompresować, a potem zrobić z nią coś użytecznego.
To „coś użytecznego” może być kosztowne. Plik JavaScript musi zostać sparsowany, skompilowany i wykonany. Plik CSS może blokować renderowanie. Plik fontu może opóźnić czytelny tekst albo spowodować przesunięcia układu. Obraz może wpływać na element Largest Contentful Paint. Skrypt zewnętrzny może przynieść własny łańcuch zależności.
Dlatego liczba żądań nadal ma znaczenie, nawet gdy pliki są małe. Skrypt o rozmiarze 3 KB może być gorszy niż obraz o rozmiarze 30 KB, jeśli blokuje renderowanie, przychodzi późno i wykonuje się na głównym wątku w złym momencie.
Jeśli czytasz raport Lighthouse i czujesz się przytłoczony dziesiątkami osobnych ostrzeżeń, zacznij od spojrzenia na waterfall żądań, a nie na pojedyncze wyniki. Mamy praktyczny przewodnik o tym, jak czytać raport Lighthouse bez paniki, ale krótka wersja brzmi: znajdź to, co blokuje pierwsze renderowanie, oraz to, co opóźnia główną treść.
„Ale HTTP/2 to naprawił” — w większości nie
HTTP/2 i HTTP/3 zmieniły ekonomię żądań. Wprowadziły multipleksowanie, kompresję nagłówków i lepsze zachowanie połączeń. Mówiąc prosto, przeglądarki stały się znacznie lepsze w wysyłaniu wielu żądań przez mniejszą liczbę połączeń.
To była realna poprawa. Zabiła też część starych nawyków, takich jak ekstremalne sprite’y CSS i ogromne sklejone bundle budowane wyłącznie po to, by omijać limity połączeń.
Ale HTTP/2 nie sprawił, że żądania stały się darmowe.
Multipleksowanie pomaga, gdy wiele zasobów współdzieli połączenie, ale przeglądarka nadal musi nadawać im priorytety. Serwery nadal muszą odpowiadać. Klient nadal musi przetworzyć każdą odpowiedź. Przeciążenie, utrata pakietów, negocjacja TLS, lookup DNS, chybienia cache i presja na główny wątek nadal istnieją.
HTTP/3 poprawia część zachowań transportu, zwłaszcza w obszarze migracji połączeń i blokowania head-of-line na warstwie transportowej. Nie usuwa kosztu odkrywania, planowania, pobierania, parsowania i wykonywania zasobów.
Dlatego współczesnym celem nie jest „spakować wszystko do jednego ogromnego pliku”. Jest nim „wysyłać mniej krytycznych żądań i sprawić, by pozostałe żądania były celowe”.
Prawda mieszka w waterfallu
Problemy wydajnościowe rzadko ujawniają się w jednej metryce. Ujawniają się jako kształt.
Otwórz panel sieciowy w przeglądarce i spójrz na pierwsze kilka sekund. Zapytaj:
- Ile żądań startuje, zanim pojawi się główna treść?
- Które żądania blokują renderowanie?
- Czy ważne zasoby są odkrywane późno?
- Czy skrypty zewnętrzne konkurują z własnym CSS, fontami lub obrazami?
- Czy wiele plików zwraca odpowiedzi 304 zamiast być podawanych bezpośrednio z cache?
- Czy ikony, fonty albo fragmenty UI są podzielone na więcej plików, niż strona potrzebuje?
Szybka strona ma zwykle nudny wczesny waterfall. Niewielka liczba krytycznych zasobów przychodzi wcześnie. Zasoby niekrytyczne czekają. Skrypty zewnętrzne są opóźnione, ograniczone albo usunięte. Przeglądarka nie jest zmuszana do żonglowania dwudziestoma priorytetami, zanim może namalować stronę.
Wolna strona często ma nerwowy waterfall: wiele małych plików, wiele originów i wiele późnych odkryć.
Mniejsze pliki nadal mają znaczenie — tylko nie wszystkie jednakowo
To nie jest argument przeciwko kompresji czy optymalizacji. To argument przeciwko optymalizowaniu bajtów przy ignorowaniu koordynacji.
Mniejsze pliki mają największe znaczenie wtedy, gdy zasób jest duży, blokuje renderowanie albo należy do ścieżki głównej treści. Na przykład:
- Obraz hero powinien mieć właściwy rozmiar i kodowanie.
- CSS blokujący renderowanie powinien być odchudzony.
- JavaScript potrzebny do pierwszej interakcji powinien być minimalny.
- Fonty powinny być podzielone na podzbiory, skompresowane i ograniczone do faktycznie używanych grubości.
Fonty są częstym przykładem. Zespoły często obsesyjnie sprawdzają, czy plik fontu ma 24 KB czy 31 KB, jednocześnie wysyłając sześć grubości, dwa style i wiele rodzin. Lepszym rozwiązaniem nie jest urwanie 7 KB z jednego pliku. Jest nim wysyłanie mniejszej liczby plików fontów. Jeśli typografia jest częścią Twojej pracy nad wydajnością, fonty webowe wciąż są jednym z najłatwiejszych zwycięstw na większości stron.
Obrazy działają według tego samego wzorca. AVIF lub WebP mogą oszczędzić istotną liczbę bajtów, ale wysyłanie dziesięciu dekoracyjnych obrazów above the fold nadal jest złym planem. Wybieraj lepsze formaty — tak — ale kwestionuj też, czy każdy obraz w ogóle musi być żądany. Przy decyzjach o formatach nasz przewodnik kiedy AVIF wygrywa z WebP, a kiedy nie jest użytecznym uzupełnieniem pracy nad liczbą żądań.
Ukryte koszty wielu małych plików
Wiele małych żądań zwykle tworzy problemy, których nie widać, jeśli patrzysz wyłącznie na łączną liczbę przesłanych bajtów.
1. Późne odkrywanie
Przeglądarki nie mogą zażądać czegoś, czego jeszcze nie odkryły. Plik CSS może odwoływać się do fontu. Skrypt może importować inny skrypt. Komponent może poprosić o JSON po hydration. Każda zależność tworzy kolejny krok w łańcuchu.
Im głębszy łańcuch, tym później zaczyna się ważna praca.
2. Narzut nagłówków
Każde żądanie i każda odpowiedź zawierają nagłówki. Kompresja nagłówków pomaga, zwłaszcza przez HTTP/2 i HTTP/3, ale nie eliminuje narzutu. Cookies mogą to znacznie pogorszyć. Jeśli Twoja strona wysyła duże cookies przy każdym żądaniu, malutkie zasoby w praktyce przestają być aż tak malutkie.
To jeden z powodów, dla których zasoby statyczne często powinny mieszkać na ścieżkach lub domenach bez cookies, a nagłówki cache zasługują na uwagę. Jeśli nagłówki zachowują się dziwnie na produkcji, debugowanie przekierowań i nagłówków HTTP jest zwykle szybsze niż zgadywanie.
3. Przerywanie głównego wątku
Wiele chunków JavaScript może tworzyć powtarzalną pracę parsowania i wykonywania. Nawet jeśli każdy chunk jest mały, przeglądarka może ciągle przerywać, by ewaluować kod. Może to pogorszyć Interaction to Next Paint i sprawić, że strona będzie wydawała się szarpana.
Użytkownika nie obchodzi, że każdy plik był mały. Obchodzi go, że kliknięcie menu zajęło 600 milisekund.
4. Złożoność cache
Dzielenie zasobów może poprawić cache, jeśli jest robione ostrożnie. Stabilny bundle vendorowy i zmienny bundle aplikacji mogą być dobrym podziałem.
Ale nadmierne chunkowanie może się zemścić. Więcej plików oznacza więcej lookupów w cache, więcej okazji do rewalidacji, więcej koordynacji wersji i więcej sposobów na przypadkowe unieważnienie zasobów, które nie musiały się zmieniać.
Bundling wrócił, ale z rozsądkiem
Pierwsza era wydajności WWW kochała bundling, ponieważ przeglądarki miały ścisłe limity połączeń. Potem pojawił się HTTP/2 i wiele zespołów mocno wychyliło się w stronę agresywnego code splittingu. Część tego była użyteczna. Część stała się przesądem.
Rozsądnym środkiem jest bundling świadomy tras.
Dla typowej strony marketingowej albo contentowej:
- Inline’uj albo ładuj tylko CSS potrzebny do początkowego renderowania.
- Utrzymuj globalny JavaScript mały.
- Unikaj dzielenia drobnych modułów na osobne żądania sieciowe.
- Opóźniaj funkcje interaktywne, które nie są potrzebne natychmiast.
- Usuwaj skrypty zewnętrzne, które nie uzasadniają swojego kosztu.
Dla aplikacji:
- Dziel według trasy lub dużej funkcji, nie według każdego komponentu.
- Utrzymuj współdzielone zależności stabilne i możliwe do cache’owania.
- Preloaduj tylko zasoby, które na pewno będą wkrótce potrzebne.
- Unikaj ładowania kodu admina, dashboardu, edytora albo eksperymentów na stronach publicznych.
- Mierz koszt interakcji, a nie tylko rozmiar bundle’a.
Bundling nie jest automatycznie dobry. Code splitting nie jest automatycznie dobry. Użyteczne pytanie brzmi: czy ten podział pomaga przeglądarce szybciej dostarczyć następne znaczące doświadczenie użytkownika?
Żądania zewnętrzne zasługują na dodatkową podejrzliwość
Żądania własne są przynajmniej pod Twoją kontrolą. Żądania zewnętrzne często są wolniejsze, mniej przewidywalne i droższe, niż wyglądają.
Pojedynczy tag manager może uruchomić analitykę, reklamy, heatmapy, widgety czatu, testy A/B, narzędzia zgód i skrypty personalizacji. Każdy dostawca może przynieść więcej żądań. Niektóre uruchomią się wcześnie. Niektóre zablokują główny wątek. Niektóre zmienią się poza Twoim procesem wydawniczym.
Najlepszą optymalizacją zewnętrznych skryptów jest usunięcie. Drugą najlepszą jest opóźnienie.
Przed dodaniem skryptu zewnętrznego zapytaj:
- Czy to musi załadować się, zanim użytkownik zobaczy stronę?
- Czy to musi ładować się na każdej stronie?
- Czy może załadować się po zgodzie, interakcji albo w czasie bezczynności?
- Kto jest za to wewnętrznie odpowiedzialny?
- Jaka metryka dowodzi, że jest to warte kosztu wydajnościowego?
Tutaj wydajność staje się zarządzaniem. Ktoś musi mieć prawo powiedzieć „nie”.
Praktyczna lista kontrolna redukcji żądań
Zacznij od stron, które mają największe znaczenie: strona główna, strona cennika, strona produktu, checkout, rejestracja albo najważniejsze landing pages. Potem przejdź przez waterfall.
Usuń
- Usuń nieużywany JavaScript i CSS.
- Usuń stare eksperymenty, porzucone piksele i zdublowaną analitykę.
- Odrzuć nieużywane grubości fontów i biblioteki ikon.
- Tam, gdzie to właściwe, zastąp obrazy dekoracyjne CSS-em.
Łącz z namysłem
- Bundle’uj drobne moduły JavaScript, które zawsze ładują się razem.
- Scal małe pliki CSS blokujące tę samą ścieżkę renderowania.
- Używaj sprite’ów SVG albo inline SVG dla powtarzanych ikon, gdy zmniejsza to liczbę żądań bez szkody dla utrzymywalności.
Odrocz
- Lazy-loaduj obrazy below the fold.
- Opóźniaj skrypty niekrytyczne do czasu po pierwszym malowaniu albo interakcji użytkownika.
- Ładuj komentarze, embedy, mapy, czat i odtwarzacze wideo tylko wtedy, gdy są potrzebne.
Cache’uj poprawnie
- Używaj długiego cache dla wersjonowanych zasobów statycznych.
- Unikaj niepotrzebnej rewalidacji plików, które rzadko się zmieniają.
- Utrzymuj HTML świeży, ale pozwól hashowanym zasobom pozostać w cache.
Zmierz ponownie
Po każdej zmianie ponownie sprawdź waterfall. Celem nie jest idealny wynik. Celem jest mniej krytycznych żądań, wcześniejsze użyteczne renderowanie i mniej zakłóceń na głównym wątku.
Jak wygląda dobry stan
Zdrowa strona niekoniecznie ma najmniej możliwych żądań. Ma małą, przemyślaną ścieżkę krytyczną.
Przeglądarka dostaje HTML, niezbędny CSS, obraz głównej treści, jeśli istnieje, być może mały skrypt wymagany do nawigacji lub interakcji above the fold, oraz minimalny zestaw fontów potrzebny, by tekst był czytelny. Cała reszta czeka na swoją kolej.
To różnica między stroną, która jest jedynie zoptymalizowana, a stroną, która wydaje się szybka.
Zmniejszanie plików nadal warto robić. Ale jeśli strona jest już rozsądnie skompresowana, kolejnym zwycięstwem wydajnościowym zwykle nie jest kolejne 2 KB oszczędzone z bundle’a. Jest nim jedno blokujące żądanie mniej, jeden plik fontu mniej, jeden skrypt zewnętrzny mniej, jeden łańcuch zależności mniej.
Mniej żądań upraszcza pracę przeglądarki. Proste jest szybkie częściej, niż lubimy przyznawać.