Web Performance

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.

The Wux Webtools Team The Wux Webtools Team 11 min czytania Wspomagane przez AI, recenzowane przez ludzi
A stylized browser waterfall showing many small requests simplified into fewer intentional requests.
Spis treści
  1. Wygodny mit „po prostu zmniejsz każdy plik”
  2. Żądania to nie tylko bajty
  3. „Ale HTTP/2 to naprawił” — w większości nie
  4. Prawda mieszka w waterfallu
  5. Mniejsze pliki nadal mają znaczenie — tylko nie wszystkie jednakowo
  6. Ukryte koszty wielu małych plików
  7. 1. Późne odkrywanie
  8. 2. Narzut nagłówków
  9. 3. Przerywanie głównego wątku
  10. 4. Złożoność cache
  11. Bundling wrócił, ale z rozsądkiem
  12. Żądania zewnętrzne zasługują na dodatkową podejrzliwość
  13. Praktyczna lista kontrolna redukcji żądań
  14. Usuń
  15. Łącz z namysłem
  16. Odrocz
  17. Cache’uj poprawnie
  18. Zmierz ponownie
  19. 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ć.

Najczęściej zadawane pytania

Czy jeden duży bundle jest lepszy niż wiele małych plików?
Nie automatycznie. Jeden ogromny bundle może opóźnić wszystko, zwłaszcza przy pierwszym ładowaniu. Wiele malutkich plików może tworzyć narzut planowania i wykonywania. Lepszy wzorzec to bundle’owanie zasobów, które zawsze są potrzebne razem, oraz dzielenie według trasy lub dużej funkcji.
Czy HTTP/2 oznacza, że liczba żądań nie ma już znaczenia?
Nie. HTTP/2 zmniejsza część narzutu połączeń dzięki multipleksowaniu i kompresji nagłówków, ale każde żądanie nadal ma koszty odkrywania, priorytetyzacji, serwera, cache, parsowania i wykonywania.
Czy powinienem inline’ować cały krytyczny CSS?
Inline’owanie niewielkiej ilości naprawdę krytycznego CSS może pomóc w pierwszym renderowaniu, ale inline’owanie zbyt dużej ilości sprawia, że HTML jest cięższy i trudniejszy do cache’owania. Utrzymaj to na minimum i zmierz efekt.
Gdzie najłatwiej zmniejszyć liczbę żądań?
Fonty i skrypty zewnętrzne często dają najszybsze zwycięstwa. Wiele stron wysyła nieużywane grubości fontów, zdublowaną analitykę, stare piksele, widgety czatu albo embedy, które nie muszą ładować się natychmiast.
Ile żądań powinna mieć strona?
Nie ma uniwersalnego celu. Mała strona contentowa powinna mieć bardzo niewiele krytycznych żądań. Złożona aplikacja może potrzebować ich więcej. Skup się na redukcji żądań przed pierwszym renderowaniem i przed główną ścieżką interakcji.

Źródła i dalsza lektura

  1. MDN Web Docs: HTTP caching
  2. web.dev: Optimize Largest Contentful Paint
  3. RFC 9113: HTTP/2
  4. HTTP Archive Web Almanac: Page Weight
O autorze
The Wux Webtools Team

Ostatnia aktualizacja:

Czytaj dalej