Dlaczego liczba zapytań HTTP zabija wydajność sklepu
Każde dodatkowe zapytanie a TTFB, FCP i LCP
Każde żądanie HTTP to osobna operacja: nawiązanie połączenia, wymiana nagłówków, pobranie zasobu. Nawet jeśli plik jest niewielki, przeglądarka musi przejść przez pełny proces komunikacji z serwerem lub CDN. Przy kilkudziesięciu lub kilkuset zapytaniach ta „biurokracja” sieciowa zaczyna dominować nad samą treścią.
Dla sklepu internetowego szczególnie istotne są trzy metryki: TTFB (Time To First Byte), FCP (First Contentful Paint) i LCP (Largest Contentful Paint). Nadmiar zapytań HTTP wpływa głównie na FCP i LCP, bo blokuje moment, w którym użytkownik widzi cokolwiek sensownego na ekranie. Zanim przeglądarka wyrenderuje nagłówek, menu i pierwszy produkt, musi pobrać CSS, JS, fonty, a często też skrypty z zewnętrznych usług.
Jeśli połowa z tych zasobów to małe, rozbite pliki dodane przez motyw, page builder i kilka wtyczek, tworzy się kolejka zapytań, która skutecznie opóźnia „pierwsze wrażenie”. Użytkownik widzi długo biały ekran lub migotanie stylów – i często po prostu zamyka kartę, zanim cokolwiek kliknie.
Ciężki plik kontra wiele małych plików
Intuicyjnie wydaje się, że lepiej mieć wiele małych plików niż jeden duży. W praktyce w świecie HTTP/1.1 tak faktycznie bywało, ale dziś trzeba patrzeć inaczej. W nowoczesnych protokołach (HTTP/2, HTTP/3) przeglądarka potrafi pobierać zasoby równolegle po jednym połączeniu, co zwiększa sens dzielenia paczek na kilka logicznych części. Nadal jednak zbyt duża liczba żądań powoduje narzut czasowy, szczególnie przy wolniejszych łączach mobilnych i dużej liczbie domen (serwer główny, CDN, serwisy zewnętrzne).
Różnica między „jednym ciężkim plikiem” a „wieloma małymi” wygląda najczęściej tak:
- jeden duży plik CSS ~ 200 KB: niewiele żądań, ale duża blokada renderu, jeśli ładowany jest synchronicznie;
- 10 plików CSS po 20 KB: więcej żądań, dłuższa kolejka, ale teoretycznie lepsza kontrola nad tym, co gdzie jest wykorzystywane;
- hybryda: mały krytyczny CSS inline + kilka plików ładowanych później – najlepszy kompromis dla e-commerce.
Przy JavaScript różnica bywa jeszcze większa. Kilkanaście skryptów z atrybutami blokującymi (bez defer i async) potrafi zatrzymać wyświetlanie strony na dłużej, niż zajmuje realne wykonanie logiki biznesowej. Dlatego liczba zapytań HTTP jest równie ważna, jak ich łączny rozmiar – oba parametry trzeba optymalizować równolegle.
Specyfika e-commerce: dlaczego sklep ma więcej requestów
Motywy i szablony e-commerce z założenia są bardziej rozbudowane niż zwykłe strony firmowe czy blogi. Na stronie pojawia się dużo komponentów funkcjonalnych:
- koszyk i mini-koszyk w nagłówku,
- rekomendacje produktów („klienci kupili też…”),
- trackingi (Google Analytics, Menedżer Tagów, piksele reklamowe),
- chaty online, widżety pomocy, formularze leadowe,
- dynamiczne filtry, sortowania, listy ulubionych,
- systemy recenzji, gwiazdki, galerie, zoom obrazków.
Każdy taki element zwykle ciągnie za sobą oddzielne pliki JS i CSS, nierzadko z kilku różnych źródeł. Do tego dochodzą obrazki, ikony, fonty webowe, preloader animowany… Nagle liczba zapytań HTTP rośnie do 150–250 na jednej podstronie produktu. Na desktopie w biurze może to jeszcze działać, ale na mobilnym LTE w ruchu – zaczyna być poważnym problemem.
W motywach premium sytuację dodatkowo komplikuje mnogość opcji. Szablon jest sprzedawany „dla wszystkich”, więc autor woli dołączyć dziesiątki modułów stylistycznych i skryptów, zamiast przycinać funkcje. Motyw ładuje zasoby nawet wtedy, gdy w Twoim sklepie dany efekt w ogóle nie jest wykorzystywany.
Wpływ liczby requestów na konwersję i koszt klienta
W praktyce każda dodatkowa sekunda do FCP i LCP obniża współczynniki konwersji, zwłaszcza na ruchu płatnym. Jeśli kampanie reklamowe przyciągają użytkownika, który czeka kilka sekund na załadowanie listy produktów lub formularza checkout, rośnie współczynnik odrzuceń, a koszt pojedynczej transakcji rośnie. Budżet mediowy jest spalany na wizyty, które kończą się, zanim strona się pokaże.
Dane z różnych badań (Google, Akamai i innych dostawców) są spójne: im wolniejsza strona, tym mniejsza konwersja. Z doświadczenia z polskimi sklepami wynika, że przyspieszenie pierwszego widoku o 1–2 sekundy potrafi dodać kilka punktów procentowych do koszyka i finalnych zamówień, bez żadnej zmiany UX czy oferty. Najczęściej osiąga się to właśnie ograniczeniem liczby zapytań HTTP i uporządkowaniem kolejności ładowania zasobów.
Kiedy optymalizacja requestów się opłaca, a kiedy nie
Optymalizacja liczby zapytań HTTP ma sens wtedy, gdy:
- sklep ma już stabilny ruch (choćby kilkaset wejść dziennie),
- TTFB jest na dobrym poziomie (serwer nie jest wąskim gardłem), a mimo to strona „klei się” przy ładowaniu,
- na kluczowych podstronach (lista, produkt, koszyk) waterfall pokazuje dziesiątki lub setki zasobów blokujących.
Jeśli sklep jest na etapie MVP, ma kilkanaście wejść dziennie i brak sprzedaży z innych powodów (asortyment, ceny, brak zaufania), głęboka optymalizacja requestów bywa przesadą. Wystarczy usunąć oczywiste śmieci: zbędne wtyczki, 2–3 najcięższe widżety, zbędne trackingi.
Natomiast przy sklepie generującym kilkadziesiąt czy kilkaset zamówień dziennie, każda redukcja liczby zapytań HTTP, która ściąga FCP/LCP w dół o sekundę, zwykle zwraca się bardzo szybko. Zwłaszcza jeśli zmiany można wdrożyć stosunkowo tanio – konfiguracją motywu, prostym pluginem optymalizacyjnym czy lekką modyfikacją szablonów, bez przebudowy front-endu od zera.
Jak policzyć i zrozumieć zapytania HTTP w istniejącym motywie
Podstawowe narzędzia: DevTools, WebPageTest, PageSpeed, GTmetrix
Pierwszym krokiem jest zawsze rzetelny pomiar. Zamiast zgadywać, ile zapytań HTTP generuje motyw i wtyczki, lepiej otworzyć kilka narzędzi i zebrać twarde dane. Najbardziej praktyczny zestaw to:
- Chrome DevTools – zakładka Network, pełna kontrola nad każdym requestem, filtrami, czasami odpowiedzi;
- WebPageTest – szczegółowy waterfall, kilka runów, różne lokalizacje i prędkości łączy;
- PageSpeed Insights (lub Lighthouse w Chrome) – szybka ocena pod kątem Core Web Vitals i rekomendacji;
- GTmetrix – wizualne przedstawienie requestów, użyteczne do szybkiego porównania przed/po optymalizacji.
Sensowny proces wygląda następująco: najpierw krótki test w PageSpeed, żeby zobaczyć ostrzeżenia. Następnie szczegółowe spojrzenie w DevTools: ile jest requestów i jakiego typu. Na końcu WebPageTest dla 2–3 kluczowych podstron sklepu, żeby zobaczyć rzeczywiste zachowanie dla użytkowników z różnych regionów i na mobile.
Filtrowanie requestów: JS, CSS, images, fonts, XHR, third-party
Sama informacja „200 zapytań HTTP” niewiele mówi. Potrzebne jest rozbicie na typy zasobów. W DevTools warto skorzystać z filtrów u góry listy Network: JS, CSS, Img, Media, Font, Doc, XHR. Dodatkowo, filtrując po kolumnie Domain, można bardzo szybko odróżnić zasoby z własnego serwera od zasobów third-party, czyli zewnętrznych integracji.
Najczęściej najwięcej requestów przypada na:
- obrazki (miniatury produktów, ikony, bannery),
- skrypty JS (motyw + wtyczki + trackingi),
- stylesheety CSS (motyw + page builder + pluginy),
- fonty (kilka wariantów z Google Fonts lub innych CDN).
Dobrym nawykiem jest zrobienie zrzutu ekranu lub eksportu listy requestów do pliku i opisanie, co jest czym. Już na tym etapie zwykle widać 3–5 głównych „śmieciarzy”: duży page builder generujący wiele małych CSS, wtyczka slidera z oddzielnym skryptem na każdej podstronie, dwa różne systemy analityczne, z których korzysta się tylko z jednego raportu.
Jak czytać waterfall i wychwycić blokujące zasoby
Widok „waterfall” w DevTools lub WebPageTest pokazuje każdy request jako poziomy pasek na osi czasu. Kluczowe jest rozróżnienie, które żądania blokują renderowanie i opóźniają FCP/LCP, a które są ładowane równolegle lub po wyświetleniu strony.
Najważniejsze sygnały, na które opłaca się zwrócić uwagę:
- długie niebieskie/zielone paski na początku (pobieranie CSS, JS bez
defer), - łańcuchy zasobów z tej samej domeny trwające po kolei (brak HTTP/2 lub zbyt wiele małych plików),
- sporo czerwonych błędów lub 301/302 (niepotrzebne przekierowania, źle podpięte zasoby),
- późno startujące skrypty zewnętrzne (chaty, trackingi) wciągające kolejne requesty.
Analizując waterfall dla strony głównej, listingu i karty produktu, można zauważyć pewien schemat: część zasobów ładuje się zawsze (globalne CSS motywu), część tylko na konkretnych typach stron (skrypty slidera na home, filtry na listingu, zoom na produkcie), a część jest w ogóle zbędna, bo ładuje się wszędzie, mimo że moduł jest wykorzystywany wyjątkowo rzadko.
Identyfikacja największych winowajców: pluginy i zewnętrzne skrypty
Najczęściej to nie sam motyw jest głównym problemem, tylko kombinacja motyw + wtyczki + dodatki zewnętrzne. Kilka przykładów z realnych wdrożeń:
- oddzielny skrypt JS i CSS dla każdej wtyczki formularza, slidera, lightboxa, mimo że w motywie jest już dostępny podobny komponent,
- dwa systemy live chatu, z których aktywny jest tylko jeden, ale oba się ładują,
- kilka tagów śledzących to samo (np. stary kod Google Analytics, nowy GA4, dodatkowo Menedżer Tagów z tym samym kodem),
- widżety social media (Facebook Page Plugin, Instagram feed) dociągające wiele kolejnych assetów.
Żeby uporządkować sytuację, przydaje się prosty audyt na bazie zrzutu z DevTools lub WebPageTest. Każdy zasób warto przypisać do źródła: motyw, wtyczka, zewnętrzny skrypt. W wielu przypadkach już sam przegląd listy pokazuje, które integracje można wyłączyć, zamienić na lżejsze lub ograniczyć do wybranych podstron (np. chat tylko na koszyku i podstronach kontaktowych).
Prosty szablon audytu requestów
Dla uporządkowania można stworzyć prostą tabelę audytu, np. w arkuszu kalkulacyjnym. Struktura, która dobrze działa w praktyce:
| Zasób | Typ | Źródło | Ładuje się gdzie | Potrzebne? | Plan działania |
|---|---|---|---|---|---|
| styles-slider.css | CSS | Wtyczka slidera | Wszędzie | Nie | Ograniczyć do strony głównej lub usunąć wtyczkę |
| chat-widget.js | JS | Chat zewnętrzny | Wszędzie | Częściowo | Ładować po 10s lub tylko na koszyku/checkout |
| theme-core.min.css | CSS | Motyw | Wszędzie | Tak | Kandydat do krytycznego CSS |
Taki arkusz można prowadzić modułami: osobno dla strony głównej, listingu, karty produktu i checkoutu. Po dwóch–trzech iteracjach lista „kandydatów do wycięcia” lub optymalizacji staje się oczywista i łatwiej zaplanować prace.
Strategia: co usuwać, co łączyć, co ładować później
Trzy główne kierunki: eliminacja, konsolidacja, odroczenie
Optymalizacja liczby zapytań HTTP w motywach e-commerce sprowadza się do trzech strategii:
- eliminacja – usuwanie zbędnych zasobów i funkcji, które realnie nie wnoszą wartości,
- konsolidacja – łączenie wielu małych plików w mniejszą liczbę większych zasobów,
- odroczenie – przesuwanie ładowania tego, co nie jest potrzebne do pierwszego widoku strony.
Na początku dobrze jest przejść po audycie requestów i oznaczyć każdy zasób jedną z trzech etykiet: usunąć, połączyć, opóźnić. Nie trzeba od razu dotykać wszystkiego – najpierw nisko wiszące owoce: duże skrypty śledzące, zbędne wtyczki wizualne, osobne pliki CSS z jedną klasą. Często już kilka decyzji „wycinamy” i „ładujemy później” daje więcej niż tygodnie dopieszczania pojedynczych reguł CSS.
Priorytety: które requesty optymalizować w pierwszej kolejności
Żeby nie rozmienić się na drobne, przydaje się prosta kolejność prac. Najwyższy priorytet mają zasoby, które są jednocześnie ciężkie, liczne i blokujące render. W praktyce na liście „do zrobienia na start” zazwyczaj lądują:
- globalne pliki CSS motywu ładowane w
<head>, - duże skrypty JS bez
defer/async(page builder, motyw, wtyczki e-commerce), - fonty webowe z kilku źródeł (np. Google Fonts + własny webfont),
- ciężkie integracje marketingowe i analityczne, które startują od razu po wejściu na stronę.
Dopiero w drugiej kolejności opłaca się zajmować drobnicą: ikonami SVG ładowanymi osobno, małymi obrazkami dekoracyjnymi czy pojedynczymi zapytaniami XHR. Te elementy też można uporządkować, ale nie kosztem prac nad plikami, które faktycznie trzymają w miejscu FCP i LCP.
Jak przekładać strategię na realne zmiany w motywie
Same założenia niewiele zmienią, dopóki nie przerobi się ich na konkretne działania. W typowym sklepie opłaca się ułożyć krótką listę zadań technicznych, np.: „usunąć wtyczkę slidera z całego serwisu poza stroną główną”, „połączyć pliki CSS page buildera w jeden bundle”, „ustawić defer na skryptach analitycznych przez GTM”. Przy prostych motywach część rzeczy da się zrobić konfiguracją (wyłączenie modułu, zmiana ustawień czcionek), reszta to kilka funkcji w pliku functions.php lub drobna wtyczka typu code snippets.
Jeżeli brakuje budżetu na duży refactoring front-endu, lepiej wdrożyć kilka szybkich usprawnień i mierzyć efekt, niż czekać na idealne rozwiązanie. Wycięcie jednego zbędnego live chatu, połączenie trzech plików CSS i opóźnienie ładowania pikseli reklamowych potrafi realnie skrócić czas do pierwszego widoku strony, bez zmiany layoutu i bez ingerencji w logikę koszyka.
Po przejściu cyklu: audyt – oznaczenie zasobów – eliminacja, konsolidacja i odroczenie ładowania, sklep zwykle przestaje być „kombajnem” ładującym kilkaset plików przy każdej odsłonie, a zaczyna zachowywać się jak uporządkowana aplikacja. Dalsze prace, takie jak wdrożenie krytycznego CSS czy bardziej zaawansowane dzielenie JavaScriptu, stają się wtedy prostym rozwinięciem już uporządkowanej bazy, a nie walką z chaosem przy każdym deployu.
Redukcja i łączenie plików CSS oraz JS w motywie
Dlaczego za dużo plików bywa gorsze niż jeden większy
Przy HTTP/2 małe pliki są mniej bolesne niż przy HTTP/1.1, ale każdy dodatkowy request to wciąż narzut: negocjacja, nagłówki, potencjalne opóźnienia DNS/TLS. W e-commerce, gdzie użytkownik przechodzi przez kilka kroków (home → listing → produkt → koszyk → checkout), ten narzut powtarza się na każdej odsłonie, jeśli cache przeglądarki nie jest jeszcze „rozgrzany”.
Motywy i page buildery lubią generować wiele drobnych zasobów:
- osobny CSS na każdy widget,
- osobny JS na każdy moduł,
- osobny plik dla RTL, mobile, „custom CSS” itd.
Jeśli każdy z nich waży kilka–kilkanaście kilobajtów, a jest ich kilkadziesiąt, to w praktyce przepłaca się za samą orkiestrację, zamiast raz przesłać większy, dobrze zcache’owany bundle.
Prosty workflow łączenia zasobów bez wielkiej przebudowy
W małym lub średnim sklepie często nie ma budżetu na pełny pipeline typu Webpack/Vite. Da się jednak osiągnąć 70–80% efektu kilkoma krokami:
- Wydzielenie „rdzenia” CSS i JS motywu – pliki wspólne dla całego sklepu (layout, siatka, typografia, koszyk, nagłówek, stopka) łączone w jeden
theme-core.cssi jedentheme-core.js. - Osobne bundle dla sekcji specyficznych – np.
home.css,listing.css,product.css, ładowane tylko tam, gdzie są potrzebne. - Minimalizacja duplikatów – jeśli wtyczka slidera ładuje swoje
slick.cssislick-theme.css, a w motywie są już style slidera, lepiej uzgodnić jedno rozwiązanie i pozbyć się bliźniaczych plików.
Technicznie można to ogarnąć nawet prostym Gulpem lub gotową wtyczką do minifikacji, która potrafi łączyć i przepinać handle w WordPressie/PrestaShopie. Ważne, żeby nie robić „super-bundla” z całego CSS i JS dla całego serwisu, bo wtedy każdy użytkownik pobiera kod koszyka i checkoutu już na stronie głównej.
Łączenie plików a cache przeglądarki i cache serwerowy
Przy łączeniu plików dobrze zgrać strategię z cache:
- długi cache dla rdzenia –
theme-core.cssitheme-core.jsz wersjonowaniem w query stringach lub w nazwie pliku (theme-core.v3.css), tak aby przeglądarka trzymała je długo, a zmiana wersji wymuszała odświeżenie, - krótszy cache dla fragmentów dynamicznych – np. bundle z elementami eksperymentalnymi, które częściej się zmieniają.
Bez tego łatwo doprowadzić do sytuacji, w której każdy drobny update CSS kasuje cały cache rdzenia i wszyscy użytkownicy pobierają na nowo gruby plik. Z punktu widzenia budżetu serwerowego i CDN to realny koszt.
Jak unikać „over-bundlingu”
Zbyt agresywne łączenie ma jedną poważną wadę: użytkownikowi karty produktu będzie serwowany kod, którego nigdy nie użyje (np. JS konfiguratora dostępnego tylko na części oferty B2B). Rozsądny kompromis wygląda tak:
- 1 bundle globalny (layout, koszyk w nagłówku, menu, modale globalne),
- 1 bundle na typ strony (home, listing, produkt, checkout),
- w niektórych sklepach 1 dodatkowy bundle dla „ciężkich” modułów (konfigurator, kreator zestawów) ładowany tylko wtedy, gdy element jest na stronie.
Mały przykład z praktyki: w jednym sklepie konfigurator produktów ładował się globalnie, bo był częścią motywu. Przeniesienie jego JS i CSS do osobnego bundle, odpalającego się tylko na 5% produktów, zmniejszyło liczbę żądań i objętość głównego JS na typowej stronie o kilkadziesiąt procent – bez zmiany UI.
Różnice między środowiskiem developerskim a produkcyjnym
Łączenie i minifikacja powinny działać na produkcji, a nie przeszkadzać w pracy developerom. Rozsądny układ to:
- w dev: wiele plików, brak minifikacji, sourcemapy, łatwe debugowanie,
- w prod: zbudowane bundle, minifikacja, brak sourcemap (albo tylko do debugowania incydentów).
W systemach typu WooCommerce, PrestaShop, Magento często da się to ustawić przełącznikiem w panelu lub przez zmienną środowiskową. Jeżeli zespół jest mały i nie ma CI/CD, minimum to prosty skrypt buildujący statyczne bundle przed wrzuceniem plików na serwer.
Krytyczny CSS, inline’owanie i opóźnianie ładowania stylów
Co faktycznie powinno być „krytyczne”
Krytyczny CSS to te style, które są potrzebne do poprawnego wyświetlenia pierwszego widoku (above the fold): nagłówek, menu, główny hero, pierwsze produkty na liście, podstawowa typografia. Wszystko poniżej pierwszego ekranu i wszelkie „wodotryski” (animacje, tooltipy, karuzele) mogą się doładować później.
Przy ograniczonym budżecie lepiej wygenerować krytyczny CSS dla kilku kluczowych szablonów niż próbować objąć nim cały serwis na raz. Najczęściej wystarczy:
- home (bo najczęściej odwiedzana),
- listing kategorii,
- karta produktu,
- koszyk/checkout (ważne dla konwersji).
Jak wygenerować krytyczny CSS bez dużej inwestycji
Są dwie sensowne ścieżki, zależnie od zasobów:
- narzędzia automatyczne – biblioteki typu
penthouse,criticallub SaaS-y generujące krytyczny CSS na podstawie URL, - pół-ręczne wydzielenie – w devtoolsach sprawdza się, które selektory są używane w pierwszym widoku, i wycina je do osobnego pliku.
W praktyce często sprawdza się miks: generator robi pierwszą wersję, a front-endowiec usuwa z niej śmieci (style dla tooltipów, dropdownów, elementów widocznych tylko po scrollu). Kluczowe jest utrzymanie krytycznego CSS w rozsądnej wielkości – jeśli plik zaczyna mieć kilkadziesiąt kilobajtów, zysk z inline’owania znika.
Inline’owanie krytycznych stylów w <head>
Najprostszy wariant wdrożenia to wstrzyknięcie krytycznego CSS bezpośrednio do <head>:
<style>
/* krytyczny CSS dla karty produktu */
body { margin: 0; font-family: system-ui, -apple-system, BlinkMacSystemFont; }
.header { ... }
.product-hero { ... }
/* itd. */
</style>
<link rel="preload" href="/assets/theme-core.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/assets/theme-core.css"></noscript>
Dzięki temu przeglądarka może od razu narysować pierwszą część strony, a reszta stylów dogrywa się w tle. Zysk w FCP/LCP bywa bardzo wyraźny, zwłaszcza przy wolniejszym łączu.
Jak nie zrobić sobie „flash of unstyled content”
Błędnie wdrożony krytyczny CSS kończy się FOUC – strona najpierw wygląda jak surowy HTML, po chwili „przeskakuje” do finalnej wersji. Żeby tego uniknąć:
- krytyczny CSS musi pokrywać dokładnie tę strukturę HTML, która widoczna jest w pierwszym widoku,
- należy unikać opóźniania wszystkich stylów; część globalnych reguł, np. typografia, siatka, powinna być albo w krytycznym CSS, albo w małym, normalnie ładowanym pliku,
- trzeba testować na prawdziwych urządzeniach i wolniejszym łączu, a nie tylko na szybkim desktopie.
W budżetowym podejściu lepiej mieć trochę grubszą warstwę krytycznych stylów i minimalne ryzyko „mrugnięcia”, niż zbyt agresywnie wycinać, oszczędzić parę kilobajtów i psuć wrażenie użytkownika.
Opóźnianie ładowania stylów drugorzędnych
Po wprowadzeniu krytycznego CSS kolejnym krokiem jest odroczenie stylów, które nie są potrzebne „na już”. Typowe kandydaty:
- style dla sekcji pod foldem (opinie, powiązane produkty, blog),
- CSS wtyczek używanych tylko w interakcji (lightbox, pop-up z newsletterem),
- style „upiększaczy” – animacje, efekty hover, micro-interactions.
Można je ładować np. przez media="print" + JS zmieniający media po załadowaniu, przez rel="preload" as="style" z późniejszą zmianą na rel="stylesheet" lub dynamicznie dołączać linki w JS po DOMContentLoaded. Wybór zależy od tego, jak bardzo chce się ograniczać ingerencję w motyw.
Optymalizacja JavaScript: defer, async, usuwanie i dzielenie kodu
Defer i async – praktyczne różnice w sklepie
defer opóźnia wykonanie skryptu do momentu, gdy HTML zostanie sparsowany. Plik ładuje się równolegle, ale nie blokuje renderowania. To podstawowe narzędzie dla skryptów, które nie muszą modyfikować DOM „w trakcie” jego tworzenia.
async oznacza, że skrypt odpali się tak szybko, jak się pobierze, bez czekania na DOM. Świetnie nadaje się do zewnętrznych integracji (trackingi, chaty), ale nie do kodu, który zakłada konkretną kolejność ładowania.
Na start najbardziej sensowne jest ustawienie defer na wszystkich własnych skryptach motywu i wtyczek, które nie muszą siedzieć w <head>. async lepiej zostawić dla analityki i reklam, które nie dotykają krytycznego layoutu.
Jak znaleźć skrypty blokujące i bezpieczne do odroczenia
Najprostsza lista kontrolna w DevTools:
- w zakładce Network sortowanie po czasie i typie „JS”,
- szukanie skryptów ładowanych w
<head>bezdefer/async, - sprawdzenie, które z nich są własne (motyw, sklep) a które zewnętrzne (GTM, piksele).
Potem krok po kroku:
- dodać
deferdo skryptów motywu i test AB na kluczowych ścieżkach (koszyk, checkout), - przenieść możliwie dużo integracji marketingowych na
asynclub ładowanie po zdarzeniu (np. scroll, interakcja), - monitorować błędy w konsoli – jeśli coś się psuje, zawęzić zakres zmian.
Usuwanie martwego i zbędnego JS
W wielu sklepach JavaScript rośnie latami, a mało kto siedzi i czyści to, co przestało być używane. Typowe „trupy”:
- stare integracje, do których nie ma już kluczy API,
- kody testów A/B zakończonych rok temu, ale nadal podpinane na każdej stronie,
- skrypty do slidera lub galerii, która już nie występuje w layoutach,
- stare biblioteki (np. dwie wersje jQuery, stare Lightboxy).
Prosty sposób na podejście do tematu:
- przejrzeć audyt requestów i wypisać wszystkie zewnętrzne JS + większe własne pliki,
- dla każdego z nich zadać pytanie: kto tego używa i gdzie?,
- jeżeli nikt nie jest w stanie wskazać użycia – odłączyć w stagingu, przetestować, a potem usunąć z produkcji.
To wygląda banalnie, ale właśnie w ten sposób często udaje się wyrzucić po kilka–kilkanaście skryptów, których nikt nie potrzebuje, a które generują requesty i dodatkowe zależności.
Dzielenie kodu JS na paczki funkcjonalne
Tak jak z CSS, sensownie jest podzielić JS według funkcji i typów stron, zamiast ładować wszystko wszędzie. Przykładowy podział:
core.js– nawigacja, koszyk w headerze, globalne modale, prosty tracking zdarzeń,listing.js– filtry, sortowanie, lazy-loading produktów,product.js– galeria, zoom, wybór wariantów, kalkulacja ceny,checkout.js– walidacja formularzy, integracje z płatnościami, wybór punktów odbioru.
Ładowanie product.js na stronie z regulaminem po prostu nie ma sensu. W systemach szablonów można to załatwić warunkami, np. w WooCommerce przez is_product(), w PrestaShop przez sprawdzenie kontrolera i przypięcie assetów tylko tam, gdzie są potrzebne.
Przy takim podziale łatwiej też wprowadzić dalszą optymalizację: najlżejszy, globalny plik JS można wpiąć jako jedyny „must-have”, a resztę dociągać warunkowo – po wykryciu typu strony albo konkretnego modułu w DOM. W wielu motywach da się to zrobić bez ciężkich zmian w bundlerze, po prostu przez kilka dodatkowych warunków w systemie szablonów i lekką refaktoryzację istniejących plików.
Dla sklepów bez rozbudowanego pipeline’u (bez Webpacka, Vite, itp.) najpraktyczniejsze jest stopniowe rozkładanie jednego, wielkiego theme.js na 2–3 pliki: „core” + 1–2 paczki pod typy stron. Po każdej takiej zmianie opłaca się przelecieć GTmetrixem lub WebPageTestem i zobaczyć, czy liczba zapytań i czas do interaktywności realnie spadły. Jeśli różnica jest kosmetyczna, nie ma sensu brnąć w coraz głębsze dzielenie kodu.
W bardziej zaawansowanych projektach dobrą dźwignią są dynamiczne importy (code splitting po stronie bundlera). Przykładowo: logikę modułu „opinie o produkcie” można ładować dopiero w momencie przewinięcia do sekcji z recenzjami albo kliknięcia w zakładkę. Klient nie czeka wtedy na JS do funkcji, z których i tak nie skorzysta na każdym wejściu na stronę produktu, a my nie dokładamy kolejnych stałych requestów.
Sporo problemów z JavaScriptem rozwiązuje też zwykła dyscyplina: jasna zasada, że każda nowa wtyczka lub fragment kodu musi mieć swoje „miejsce” (konkretny plik/paczka) i warunek ładowania. Taki prosty „regulamin assetów” ogranicza chaos, dzięki czemu za pół roku ktoś jest jeszcze w stanie zrozumieć, co do czego służy i co wolno usunąć bez ryzyka wysadzenia koszyka czy checkoutu.
Jeżeli celem jest szybszy, tańszy w utrzymaniu sklep, redukcja zapytań HTTP przez czyszczenie, łączenie i mądre opóźnianie CSS oraz JS zwykle daje lepszy zwrot z czasu niż efektowna przebudowa frontu. Kilka wieczorów poświęconych na audyt zasobów, wycięcie zbędnych skryptów i poukładanie stylów pod krytyczny CSS potrafi skrócić ładowanie o sekundy, bez przepisywania motywu od zera i bez kosztownych migracji na nowe stacki.
Wstrzykiwanie skryptów „na żądanie” zamiast na każdej stronie
Po uporządkowaniu paczek JS kolejnym krokiem jest ograniczenie ich ładowania do momentu, gdy rzeczywiście są potrzebne. Zamiast podpinać wszystkie skrypty w szablonie globalnym, lepiej dogrywać część z nich dynamicznie – po akcji użytkownika lub po spełnieniu konkretnego warunku.
Typowe przypadki, gdzie to ma sens:
- moduły opinii i Q&A pod produktem – JS może wczytać się dopiero po przewinięciu do sekcji recenzji,
- widgety „czatu na żywo”, które można odpalić dopiero po kilku sekundach lub po drugiej odsłonie,
- cięższe integracje marketing automation, które są potrzebne głównie przy dłuższych sesjach.
Nawet bez bundlera da się to zrobić prostym loaderem:
function loadScript(src, callback) {
if (document.querySelector('script[src="' + src + '"]')) return;
var s = document.createElement('script');
s.src = src;
s.async = true;
s.onload = callback || null;
document.head.appendChild(s);
}
// przykładowo: ładuj chat dopiero po 10 sekundach
setTimeout(function () {
loadScript('https://example.com/chat.js');
}, 10000);Takie rozwiązania nie wymagają przebudowy motywu. Wstawia się jeden mały loader w globalnym JS, a resztę integracji przenosi z szablonów do wstrzykiwania „na żądanie”. Najwięcej zyskują sklepy z dużą liczbą dodatków, gdzie każdy „ficzer” ma swoją wtyczkę.
Ładowanie integracji analitycznych i reklam z opóźnieniem
Analityka, piksele reklamowe i systemy tagów potrafią wygenerować więcej requestów niż cały motyw. Zamiast akceptować to jako „zło konieczne”, lepiej wprowadzić prosty porządek:
- podstawowa analityka (np. jedno narzędzie) wchodzi od razu, ale na
async, - dodatkowe systemy (heatmapy, eksperymenty, remarketing) ładują się dopiero po pierwszej interakcji lub po kilku sekundach,
- część tagów idzie przez jeden kontener (GTM), żeby nie wstrzykiwać kilku bibliotek osobno.
Minimalny wariant: prosta logika „po scrollu” lub „po kliknięciu”:
var analyticsLoaded = false;
function initExtraAnalytics() {
if (analyticsLoaded) return;
analyticsLoaded = true;
loadScript('https://example.com/heatmap.js');
loadScript('https://example.com/remarketing.js');
}
window.addEventListener('scroll', initExtraAnalytics, { once: true });
document.addEventListener('click', initExtraAnalytics, { once: true });W praktyce część użytkowników, którzy tylko „zaglądają” i od razu wychodzą, nie generuje dodatkowych requestów. Właśnie tam robi się najtańsze oszczędności w liczbie zapytań.
Minimalizacja liczby zapytań do czcionek webowych
Czcionki to kolejna grupa requestów, która łatwo wymyka się spod kontroli – szczególnie przy gotowych motywach, które z automatu ładują kilka rodzin z Google Fonts w różnych odmianach. Z punktu widzenia czasu i serwera wystarczą zwykle 1–2 rodziny w 2–3 odmianach wag.
Najprostszy plan porządkowania:
- sprawdzenie w DevTools, ile realnie fontów jest ładowanych (rodzina, waga, styl),
- zawarcie „umowy” z grafikiem/marketingiem, które z nich są krytyczne, a które dekoracyjne,
- wycięcie nieużywanych wag i stylów, a dodatkowe fonty zastąpienie systemowymi (np. dla drobnych elementów UI).
Jeśli motyw korzysta z Google Fonts, zapytania można ograniczyć choćby tak:
- połączyć kilka rodzin i wag w jednym wywołaniu URL zamiast kilku osobnych linków,
- dodać
display=swap, żeby tekst nie czekał blokująco na font, - w przypadku dużego ruchu i powtarzalnych odsłon – rozważyć samodzielne hostowanie fontów.
Hosting lokalny jest rozsądny dopiero wtedy, gdy ktoś może to zrobić raz a porządnie: wygenerować podzbiór znaków, zbudować @font-face z kilkoma formatami i ustawić długie cache. Bez tego łatwo dorobić się jeszcze większej liczby plików niż przy domyślnym Google Fonts.
Sprytne korzystanie z cache przeglądarki i CDN
Nawet najlepsze cięcia w CSS/JS niewiele zmienią, jeśli przeglądarka przy każdym wejściu musi pobierać wszystko od zera. Dobrze ustawione nagłówki cache i prosty CDN często redukują realny „ból” liczby zapytań, bo wiele z nich po pierwszej wizycie znika w lokalnym cache.
Podstawowe działania, które nie wymagają zmiany motywu:
- ustawienie długiego cache (np. 1–12 miesięcy) dla statycznych zasobów połączone z versioningiem w nazwach plików,
- cache krótszy dla plików, które zmieniają się częściej (np. generowany CSS z panelu, dynamiczne sprite’y),
- podpięcie CDN dla grafik produktowych, miniatur i assetów motywu.
Jeśli po stronie hostingu brakuje wygodnego panelu do cache, najprościej wymusić zmiany plików przez „cache busting” w adresie. W wielu systemach wystarczy dopisać numer wersji do URL w szablonie:
<link rel="stylesheet" href="/assets/theme-core.css?v=20240501">Po zmianie pliku podnosi się numer wersji, a przeglądarka traktuje to jako nowy zasób. Wariant budżetowy, ale działa i nie wymaga kombinowania z automatyzacją buildów.
Optymalizacja obrazów pod kątem liczby requestów
Choć głównym tematem jest CSS i JS, w realnym sklepie to właśnie obrazki generują największy „las” requestów. Kilka prostych zmian potrafi przyciąć liczbę zapytań bez wywracania frontu:
- miniatury produktów w jednym rozmiarze na widoku listy – bez mieszania kilku różnych wymiarów na tej samej siatce,
- lazy-loading dla grafik pod foldem (atrybut
loading="lazy"jako najprostsza opcja), - łączenie drobnych ikon w sprite SVG lub zestaw ikon z jednej biblioteki zamiast kilkunastu pojedynczych plików PNG/SVG.
W gotowych motywach często zostaje „spadek” po starych rozwiązaniach: osobne pliki PNG dla kilkunastu flag językowych, ikon social media, różnych strzałek. Jeśli zastąpi się je jednym sprite’em SVG albo ikonami z systemowego fontu, kilka–kilkanaście requestów znika praktycznie za darmo.
Lazy-loading elementów bogatych w JS
Różne „fajerwerki” frontowe – carouselle, mapy, konfiguratory – potrafią ściągać dodatkowe biblioteki JS i CSS. Gdy są od razu widoczne w pierwszym widoku, trzeba je zoptymalizować. Gdy są niżej, idealnie nadają się do ładowania warunkowego.
Przykładowe elementy do odroczenia:
- mapy punktów odbioru,
- zaawansowane konfiguratory produktów,
- sekcje „inspiracje”, „Instagram feed”, „ostatnio oglądane”.
Przy braku zaawansowanego bundlera wystarczy prosty mechanizm oparty o Intersection Observer:
var moduleLoaded = false;
function initRichModule() {
if (moduleLoaded) return;
moduleLoaded = true;
loadScript('/assets/rich-module.js', function () {
if (window.initRichSection) {
window.initRichSection();
}
});
}
var target = document.querySelector('.js-rich-section');
if ('IntersectionObserver' in window && target) {
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
initRichModule();
observer.disconnect();
}
});
});
observer.observe(target);
}Efekt jest prosty: dopóki użytkownik nie przewinie do sekcji, silnik JS nie pobiera dodatkowych plików. W statystykach requestów i czasie do interaktywności widać to bardzo szybko.
Ograniczanie liczby wtyczek i modułów frontowych
Gotowe wtyczki ratują czas, ale każda z nich to potencjalne nowe pliki CSS i JS. Jeśli na stronie produktu działają trzy różne karuzele, wyskakują dwa pop-upy i do tego dochodzi kilka widgetów social proof, zrobi się nie tylko wolno, ale też drogo w rozwoju.
Przy porządkowaniu opłaca się dążyć do zasady: jedna funkcja – jedno narzędzie. Przykładowo:
- jeden slider/karuzela wykorzystywany w kilku miejscach zamiast trzech różnych bibliotek,
- jeden system pop-upów dla newslettera, regulaminów, komunikatów zamiast kilku osobnych,
- jeden moduł social proof zamiast osobnych „ostatnio kupione”, „tylko X sztuk”, „Y osób ogląda”.
Technicznie wygląda to tak, że najpierw robi się spis wtyczek frontowych z podziałem na funkcje, potem wybiera się te, które naprawdę dają zwrot (np. realny wzrost konwersji) i zostawia jedno rozwiązanie na kategorię. Reszta do wyłączenia i usunięcia assetów. Dla małych zespołów to zwykle weekend pracy, a redukcja requestów – bardzo odczuwalna.
Prosty „budżet zapytań HTTP” dla motywu
Bez twardego limitu liczba zapytań rośnie prawie samoistnie. Każdy nowy ficzer „dokłada” swój CSS i JS, nikt natomiast nie zdejmuje starych. Dobrym hamulcem jest własny, prosty budżet HTTP – nawet jeśli nie jest idealnie policzony.
Przykładowy budżet dla strony produktu może wyglądać tak:
- do X requestów na JS (np. 5–7 plików, wliczając zewnętrzne),
- do Y requestów na CSS (np. 3–4, w tym krytyczny + łączony resztkowy),
- limit dodatkowych zewnętrznych integracji na widok (np. 2–3, reszta tylko na kluczowych ścieżkach).
Za każdym razem, gdy ktoś chce dorzucić nową wtyczkę, trzeba odpowiedzieć na pytanie: skąd „zdejmiemy” inne requesty, żeby utrzymać budżet. Jeśli nikt nie potrafi wskazać, co można wyrzucić, integracja czeka na refaktoryzację lub wchodzi tylko na część ruchu (np. bez mobile).
To podejście jest mało efektowne na prezentacjach, ale długofalowo robi różnicę. Zamiast co rok przepisywać motyw, da się go utrzymać w ryzach małymi, regularnymi cięciami – a liczba zapytań HTTP pozostaje pod kontrolą nawet przy rozbudowie sklepu.

Jak policzyć i zrozumieć zapytania HTTP w istniejącym motywie
Zanim cokolwiek się tnie, trzeba mieć twarde liczby. „Wolno działa” nic nie znaczy, dopóki nie wiadomo, ile faktycznie requestów wysyła strona i które z nich są najdroższe. Na szczęście narzędzia potrzebne do takiego audytu są darmowe i wbudowane w przeglądarkę.
Pomiar w DevTools: Network i Performance
Najbardziej bezpośredni obraz sytuacji daje zakładka Network w Chrome DevTools (lub odpowiednikach w innych przeglądarkach). Prosty schemat pracy:
- otwarcie strony (np. produkt, kategoria, koszyk) z włączonym DevTools,
- w zakładce Network ustawienie filtra „All” i odznaczenie „Disable cache” tylko wtedy, gdy symulujemy pierwszą wizytę,
- odświeżenie strony z wciśniętym Ctrl+F5 (twardy reload).
Na dole listy widać liczbę requestów i łączny transfer. Dobrze jest zrobić zrzuty ekranu dla kilku kluczowych widoków: strona główna, lista produktów, karta produktu, koszyk, checkout. To staje się podstawą do budżetu i porównań po zmianach.
Dla front-endu bardziej użyteczna jest także zakładka Performance. Pokazuje, kiedy faktycznie HTML jest parsowany, kiedy dochodzą CSS/JS i ile czasu blokują one interaktywność. Jeśli duża grupa requestów pojawia się dopiero w drugiej połowie wykresu, czasem wystarczy je opóźnić, zamiast na siłę łączyć.
Grupowanie requestów według typu i źródła
Surowa liczba requestów to dopiero początek. Żeby sensownie ciąć, trzeba zgrubnie posegregować zasoby:
- HTML – zwykle 1–3 requesty (główny widok + ewentualne AJAX-y krytyczne),
- CSS – arkusze motywu, wtyczek, fonty zewnętrzne,
- JS – skrypty główne, wtyczki, integracje, tag manager,
- Images – zdjęcia produktów, backgroundy, ikony, logotypy,
- Fonts – pliki z Google Fonts lub hostowane lokalnie,
- Other – requesty do API, pikseli analitycznych, trackingu itp.
W zakładce Network można filtrować po typach (JS, CSS, Img, Font) i po domenie (kolumna „Domain”). To pozwala od razu zobaczyć, ile żądań pochodzi z własnej domeny, a ile z zewnętrznych źródeł: Google, Facebook, narzędzia martech, płatności, mapy.
Praktyczny trik przy budżecie: spisać w arkuszu kalkulacyjnym sumy dla każdej kategorii per widok. Dzięki temu widać, że np. na karcie produktu główny problem to 40+ requestów do obrazków, a na liście kategorii – 15 oddzielnych plików JS z wtyczek.
Analiza wodospadu (waterfall) i „winnych” requestów
Wodospad w Network pokazuje kolejność i czas trwania poszczególnych requestów. Kilka rzeczy mocno rzuca się w oczy przy motywach e-commerce:
- długie, blokujące ładowanie głównego CSS,
- szereg plików JS wczytywanych synchronicznie jeden po drugim,
- opóźnione ładowanie zdjęć w sliderze hero, które blokują postrzeganie ładowania,
- wiele małych ikon z osobnymi requestami zamiast sprite’a.
Jeżeli w wodospadzie widać całe „grona” żądań z tej samej ścieżki (np. /wp-content/plugins/slider-x/ albo /modules/social-proof/), to sygnał, że optymalizację warto zacząć od jednej wtyczki, zamiast rozkopywać cały motyw. Czasem usunięcie jednego modułu potrafi zabrać 10–20 requestów.
Monitoring w czasie: nie tylko jednorazowy audyt
Jak już raz uda się zbić liczbę zapytań, problem lubi wrócić po kilku miesiącach – każda kampania, nowa integracja, kolejne A/B testy dokładają swoje assety. Dlatego opłaca się mieć prosty, powtarzalny sposób monitoringu.
Dla małego sklepu wystarczy kwartalny „przegląd techniczny” z użyciem Lighthouse lub WebPageTest (wersje darmowe). Można porównać „Total requests” i „Transfer size” z poprzednim raportem. Jeśli liczba rośnie o kilkadziesiąt procent, to znak, że motyw został obudowany dodatkami i trzeba wrócić do sprzątania.
Na start dobrym kompromisem jest zapisywanie raportu Lighthouse jako JSON/HTML i trzymanie go w prostym repo lub folderze w chmurze. Zestawienie dwóch raportów rok do roku daje argumenty w rozmowach z marketingiem i zarządem, dlaczego czegoś nie warto „doklejać na szybko”.
Strategia: co usuwać, co łączyć, co ładować później
Cięcie requestów bez planu kończy się chaosem. Jedne pliki zostają połączone „na siłę”, inne wciąż ładują się na wszystkich podstronach, a część integracji i tak wchodzi od razu. Sensowna strategia dzieli zasoby na trzy zbiory: do usunięcia, do łączenia i do odroczenia.
Pierwszy etap: „co można po prostu wyrzucić”
Najbardziej opłacalne godzinowo są rzeczy, które da się wyłączyć w panelu bez dotykania kodu. Przykładowe kandydaty:
- widgety social media na każdej podstronie,
- zbędne slidery i karuzele w sekcjach, gdzie nikt nie przewija,
- podpięte, ale nieużywane integracje (chat, heatmapy, stare systemy rekomendacji),
- stare A/B testy, które dawno zakończono, ale tag wciąż wisi.
Najprościej podejść do tego biznesowo: przejrzeć listę narzędzi i poprosić właściciela sklepu o wskazanie, które z nich realnie wykorzystuje się w działaniach. Wszystko, co „kiedyś było testowane” i nie ma właściciela po stronie biznesu, z reguły tylko generuje requesty i konflikty w konsoli.
Drugi etap: łączenie plików „rdzeniowych”
Pliki, które są potrzebne na większości widoków (layout, podstawowa logika koszyka, wyświetlanie siatki produktów), zwykle najlepiej zbić w kilka większych paczek:
- jeden główny CSS motywu,
- jeden lub dwa główne pliki JS (np. „core” + „komponenty UI”),
- jedna paczka ikon (sprite SVG lub icon font).
W systemach typu WooCommerce, Presta czy Shopify można to ogarnąć nawet prostymi skryptami buildowymi (np. Gulp, npm scripts) bez wprowadzania całego Webpacka czy Vite. Ważniejsze od narzędzia jest zachowanie porządku: żeby nie łączyć wszystkiego w jeden monolit, który rośnie bez kontroli, tylko utrzymywać 2–3 logiczne paczki.
Jeśli obecny motyw ładuje np. 7 plików CSS i 10 plików JS na każdej stronie, realna redukcja do 2–3 CSS + 2–3 JS już robi wyczuwalną różnicę, zwłaszcza na mobilnym LTE.
Trzeci etap: ładowanie warunkowe i „późniejsze”
Ostatnia grupa to zasoby, które nie są krytyczne dla pierwszego widoku, albo które są potrzebne tylko w określonych miejscach. Tutaj wchodzą techniki:
- ładowanie skryptów dopiero po interakcji (scroll, klik),
- modułowe bundlowanie per widok (np. osobny plik tylko dla karty produktu),
- lazy-loading dla zewnętrznych widgetów (mapy, recenzje, social feed).
W wielu sklepach spory zysk daje wyłączenie części integracji poza kluczowymi ścieżkami. Przykładowo: heavy-widget z opiniami może wchodzić tylko na karcie produktu i w koszyku, ale już nie na blogu czy stronie informacyjnej.
Priorytety: co ruszać jako pierwsze
Żeby nie rozdrabniać się na małe zyski, warto ustawić priorytety według trzech kryteriów:
- Wpływ na biznes – strona produktu, koszyk, checkout zawsze są przed stroną „O nas”.
- Koszt wdrożenia – najpierw zmiany konfiguracji i wyłączenie wtyczek, dopiero potem refaktoryzacja motywu.
- Skala problemu – jeśli slider hero ściąga 15 requestów, a ikonka social media jeden, wiadomo, od czego zacząć.
W praktyce przy małych zespołach taki plan sprowadza się do kilku prostych questów: „wyczyść wtyczki”, „zbierz CSS”, „ogarnij lazy load JS”, rozłożonych na 2–3 sprinty. Każdy krok daje mierzalny spadek liczby zapytań.
Redukcja i łączenie plików CSS oraz JS w motywie
Same decyzje biznesowe nie zmniejszą requestów, jeśli pliki w motywie są porozrzucane po dziesięciu miejscach. Nawet w gotowych szablonach da się jednak zrobić podstawowy porządek bez pisania wszystkiego od zera.
Inwentaryzacja CSS: „czy to jeszcze ktoś używa?”
Większość dojrzałych motywów ma ten sam problem: lata łatek i poprawek zostawiają martwe klasy, stare komponenty i osobne pliki z „hotfixami”. Pierwszy krok to inwentaryzacja:
- lista wszystkich plików CSS ładowanych na kluczowych widokach (z Network),
- oznaczenie, które z nich pochodzą z motywu, a które z wtyczek,
- sprawdzenie w kodzie, skąd każdy plik jest włączany (functions.php, konfiguracja modułu, szablon).
Do wyszukania nieużywanych selektorów można na start użyć prostych narzędzi typu „Coverage” w DevTools (zakładka Sources → Coverage). Pokazuje ono, jaka część pliku CSS faktycznie została użyta przy renderowaniu strony. W wielu przypadkach widać, że osobny arkusz dla małego widgetu jest w 90% zbędny na większości widoków.
Łączenie CSS bez agresywnej minifikacji
W obiegowej opinii optymalizacja CSS to zawsze minifikacja i wyrafinowany pipeline. Tymczasem przy małych i średnich sklepach większy efekt daje samo połączenie plików i sensowne cache, nawet jeśli formatowanie kodu zostaje prawie nietknięte.
Prosty wariant „budżetowy”:
- stworzenie jednego głównego pliku, np.
theme.css, który importuje lub wchłania style layoutu, typografii i podstawowych komponentów, - utworzenie jednego pliku „dodatkowego”, np.
widgets.css, dla mniej istotnych elementów UI (które można wczytać z mniejszym priorytetem), - zastąpienie wielu
<link>w szablonie jednym lub dwoma.
Minimalna minifikacja (usunięcie komentarzy, zbędnych spacji) może być zrobiona nawet przez prosty skrypt w CI lub wtyczkę w panelu, ale nie jest warunkiem koniecznym, żeby zobaczyć spadek liczby requestów.
Inwentaryzacja JS: co działa, co jest „po dziadku”
Przy JavaScript warto zacząć od mapy skryptów:
- jakie pliki JS są ładowane globalnie (na każdej stronie),
- jakie tylko na wybranych widokach (kategoria, produkt, checkout),
- które pochodzą z motywu, a które z wtyczek i zewnętrznych integracji.
Potem trzeba prześledzić zależności. Przykładowo: kilka skryptów może polegać na jQuery, więc nie da się go tak po prostu wyrzucić, ale można ograniczyć, gdzie jest wczytywane. Często pojawiają się „zabytki” typu stara wersja slidera, która już nie jest nigdzie inicjalizowana, ale plik wciąż się ładuje.
Łączenie JS z zachowaniem podziału logicznego
Łączenie JavaScriptu w jednym ogromnym pliku kusi prostotą, ale w praktyce robi się trudno zarządzalne. Lepszy podział to:
- core.js – podstawowe funkcje, które są potrzebne na każdej stronie (menu, koszyk mini, formularze, małe interakcje),
- product.js – skrypty typowe dla karty produktu (galeria zdjęć, warianty, konfiguratory),
- checkout.js – logika checkoutu (walidacje, integracje płatności),
- extras.js – moduły niekrytyczne (widgety, animacje, social proof), ładowane później.
W gotowych motywach bez bundlera można to zrobić nawet ręcznie: skopiować zawartość kilku mniejszych plików do jednego, usystematyzować kolejność i zaktualizować wywołania w szablonach. Nie jest to eleganckie, ale szybko daje efekt w wynikach Network.
Śledzenie regresji po łączeniu plików
Po każdej większej zmianie w strukturze CSS/JS trzeba sprawdzić, czy coś nie przestało działać. Minimalny, ale skuteczny plan testów:
- scenariusz przejścia całej ścieżki zakupowej (wejście → produkt → koszyk → checkout),
- sprawdzenie kilku kluczowych interakcji: dodanie do koszyka, filtracja na liście produktów, logowanie, użycie kuponu,
- sprawdzenie w dwóch–trzech najczęściej używanych przeglądarkach (Chrome, Safari, mobilny Chrome).
Jeśli zespół nie ma automatycznych testów E2E, wystarczy prosty „checklist” w Notion czy arkuszu, który odhacza się przy każdym większym deployu. Dobrze jest też przed i po zmianach zrobić zrzut z Lighthouse lub WebPageTest i zapisać wyniki – wtedy od razu widać, czy redukcja liczby plików naprawdę skróciła czas ładowania, czy tylko przesunęła problem w inne miejsce.
Krytyczny CSS, inline’owanie i opóźnianie ładowania stylów
Po uporządkowaniu i połączeniu plików CSS kolejne łatwe punkty dochodzą z tym, kiedy style są ładowane. Nawet jeśli na koniec nadal zostaną 2–3 pliki, można przyspieszyć pierwszy render, ładując niewielki fragment jako „krytyczny CSS”, a resztę stylów ładować niższym priorytetem.
Najprostszy wariant bez drogich narzędzi: ręcznie wyciągnąć do osobnego pliku lub bezpośrednio do <style> w <head> style potrzebne do zbudowania pierwszego widoku nad linią załamania (nagłówek, nawigacja, główna siatka produktów, podstawowa typografia). Taki blok zwykle ma kilkanaście–kilkadziesiąt kilobajtów. Reszta stylów może być trzymana w jednym głównym pliku ładowanym standardowo, ale z preload lub „asynchronicznym” wpięciem przez mały skrypt, tak by nie blokowały renderowania.
W praktyce wygląda to tak, że motyw ma jeden niewielki blok CSS inline w <head>, a dopiero po nim ładuje się główny arkusz. Użytkownik widzi szybko „szkielet” strony w poprawnej formie, a drobne rzeczy typu animacje, rzadkie widoki czy specyficzne layouty doskakują pół sekundy później. Ten kompromis mocno poprawia TTFB → FCP z niewielkim nakładem pracy – często wystarczy raz przejść przez stronę główną i kartę produktu, sprawdzić w DevTools, które klasy są używane, i te style przenieść do sekcji krytycznej.
Jeśli budżet pozwala na mały krok dalej, można wykorzystać gotowe generatory critical CSS w postaci usług SaaS albo prostych CLI (np. odpalanych lokalnie raz na jakiś czas). Przy małych sklepach nie ma sensu generować krytycznych stylów osobno dla dziesiątek widoków – zazwyczaj wystarczy osobne podejście dla strony głównej, listy produktów i pojedynczego produktu. Reszta ruchu i tak skupia się wokół tych szablonów.
Połączenie porządków w plikach, sensownego łączenia oraz kontrolowanego ładowania krytycznego CSS i warunkowego JS zwykle wystarcza, żeby z ciężkiego motywu zrobić całkiem żwawy sklep – bez wymiany systemu, bez rewolucji technicznej i bez wielomiesięcznego przepisywania. Krok po kroku, od inwentaryzacji po małe pakiety zmian, można systematycznie ciąć liczbę zapytań HTTP i odzyskiwać cenne sekundy na ścieżce zakupowej.
Optymalizacja JavaScript: defer, async, usuwanie i dzielenie kodu
Po zebraniu i posegregowaniu skryptów przychodzi moment decyzji: które z nich faktycznie muszą blokować render, a które można spokojnie zepchnąć na później. Przeglądarka traktuje domyślne <script> jak hamulec ręczny – dopóki plik się nie pobierze i nie wykona, parser HTML musi czekać. W sklepie oznacza to wolniejszy „pierwszy obraz” strony.
Defer: domyślny tryb dla skryptów motywu
Najbardziej opłacalnym ustawieniem dla większości skryptów motywu jest defer. Dzięki niemu:
- skrypty ładują się równolegle z HTML,
- wykonują się dopiero po zbudowaniu drzewa DOM,
- zachowują kolejność względem siebie.
Dla typowego e-commerce oznacza to, że layout, nagłówek, siatka produktów mogą pojawić się na ekranie, zanim JS zajmie się interakcjami. Do takiego trybu nadaje się m.in. core.js, logika layoutu, prosty minisummary koszyka, drobne walidacje formularzy, a nawet skrypty produktów, o ile nie zmieniają samego HTML-a przed pierwszym renderem.
Praktyczny sposób wdrożenia w systemach typu WordPress lub innych CMS:
- ustalić listę „bezpiecznych” skryptów (motyw + część wtyczek),
- w funkcji rejestrującej skrypty dodać
deferdo ich tagów, - zostawić bez zmian tylko te, które rzeczywiście muszą wykonać się natychmiast (np. krytyczne A/B testy, jeśli są).
Przed globalnym dodaniem defer dobrze jest przetestować ścieżkę zakupową z wyłączonym JS (lub z mocno opóźnionymi skryptami). Jeśli strona całkowicie się rozsypuje, trzeba punktowo dopracować HTML/CSS, żeby kluczowe funkcje były przynajmniej częściowo używalne bez natychmiastowego JS.
Async: tylko dla naprawdę niezależnych skryptów
async kusi, bo jeszcze mocniej „rozluźnia” skrypty, ale łatwo nim zrobić bałagan. Pliki z async wykonują się w momencie pobrania, w nieprzewidywalnej kolejności. Dla większości kodu motywu to zbyt ryzykowne.
Gdzie async ma sens:
- zewnętrzne analityki (np. część bibliotek marketingowych),
- piksele reklamowe, które tylko nasłuchują zdarzeń,
- widgety, które działają całkowicie obok głównej logiki sklepu (np. czat online, pop-up opinii).
Dla takich skryptów najlepiej przygotować osobny blok „marketingowy” i ładować go async lub przez tag managera z opóźnieniem, zamiast mieszać je z logiką checkoutu czy koszyka. W mniejszych sklepach proste opóźnienie o kilka sekund po załadowaniu strony potrafi znacząco odciążyć start, a nie wpływa na raporty – użytkownik zdąży zjechać stronę o kilka pikseli, zanim dojdzie do pierwszej akcji liczonej w analityce.
Selektywne usuwanie i „usypianie” JS
Po wprowadzeniu defer i ewentualnego async widać, które skrypty dalej generują zbyt dużo requestów. Zamiast od razu pisać wszystko na nowo, lepsze są małe cięcia w trzech kategoriach:
- Wyłączenie całych modułów – np. zbędne animacje scrollu, karuzele opinii, zbieracze statystyk z zerowym użyciem.
- Usunięcie nieużywanych integracji – stare piksele kampanii, wygasłe czaty, dawne systemy rekomendacji.
- Usypianie skryptów do momentu interakcji – ładowanie plików dopiero po kliknięciu w element, który ich potrzebuje.
Przykładowo: chat online można załadować dopiero po kliknięciu w dymek w rogu, a nie razem z resztą strony. W praktyce robi się to małym skryptem inline w HTML, który po kliknięciu dynamicznie dodaje tag <script src="…">. Większość użytkowników i tak nie skorzysta z chatu, a przeglądarka ma mniej roboty przy pierwszym wczytaniu.
Podział kodu według widoków zamiast jednego „potwora”
Duży, zminifikowany plik JS wygląda efektywnie na papierze, ale jeśli 60% jego zawartości jest potrzebne tylko na karcie produktu, ładowanie go globalnie na każdej podstronie to marnotrawstwo.
Praktyczna strategia dla sklepów bez rozbudowanego bundlera:
- umieścić w
core.jswyłącznie kod wspólny, - wyciągnąć logikę produktów do
product.js, - checkout trzymać w osobnym
checkout.js, - całą „rozrywkę” (animacje, efekty, dodatki) trzymać w
extras.js.
Potem w szablonach warunkowo włącza się poszczególne pliki: lista produktów nie potrzebuje logiki checkoutu, podobnie jak blog nie musi ładować galerii 360° z widoku produktu. Taki podział bywa bardziej pracochłonny niż samo połączenie plików, ale często daje większy efekt w metrykach i zmniejsza ryzyko późniejszych konfliktów między funkcjami.
Lazy loading dla ciężkiego JS
Niektóre moduły JS są ciężkie z definicji: konfiguratory, personalizatory, zaawansowane filtry, wideo. Zamiast ładować je zawsze na starcie, można wdrożyć prosty lazy load:
- element z „placeholderem” (np. przycisk „Włącz konfigurator”),
- po interakcji dynamiczne dociągnięcie pliku JS z
fetch()lub przez dodanie<script>, - start modułu dopiero po pełnym pobraniu.
To rozwiązanie rozsądnie sprawdza się w produktach, z których korzysta tylko część klientów – np. zaawansowane konfiguracje B2B. Zamiast spowalniać każdą wizytę, moduł łączy się z serwerem dopiero, gdy ktoś faktycznie go potrzebuje.
Minimalne bezpieczeństwo przy „agresywnym” odkładaniu JS
Odkładanie JS w czasie można zrobić źle, jeśli zignoruje się scenariusze brzegowe. Kilka prostych zasad trzyma temat w ryzach:
- elementy kluczowe (dodanie do koszyka, wybór wariantu) powinny być choć częściowo obsługiwalne bez JS lub z prostym fallbackiem (np. zwykły
<form>z przeładowaniem strony), - walidacja formularzy po stronie klienta jest dodatkiem – logika serwerowa ma być nadrzędna,
- moduły marketingowe nie mogą blokować przejścia przez checkout, nawet jeśli całkiem się wysypią.
Przy każdej większej zmianie w kolejności i sposobie ładowania JS dobrze jest zasymulować wolne łącze (np. w DevTools) i wolny telefon. Daje to obraz tego, co faktycznie widzi użytkownik, zanim wszystko się dogra. Widać wtedy, czy przycisk „Dodaj do koszyka” nie jest przez kilka sekund martwy, bo JS dopiero się ładuje.
Małe helpery zamiast ciężkich bibliotek
Stare motywy często ciągną całe biblioteki dla jednego efektu – jQuery dla dwóch animacji, kompletny moment.js dla prostego formatowania daty. W dobie nowoczesnego JS można sporo odchudzić:
- zastąpić jQuery prostymi helperami napisanymi „na miarę” (querySelector, classList itp.),
- użyć natywnych API przeglądarek zamiast dużych polyfilli, jeśli docelowe przeglądarki klientów na to pozwalają,
- w przypadku pojedynczych efektów wziąć małe, wyspecjalizowane biblioteki zamiast frameworków all-in-one.
Najrozsądniejsze podejście: zacząć od miejsc, gdzie biblioteka jest używana najmniej, i tam ją wypchnąć w pierwszej kolejności, zamiast od razu przepisywać cały motyw. Nawet usunięcie jednego globalnego jquery-migrate.js czy kilku starych polyfilli potrafi zredukować liczbę requestów i łączny rozmiar JS o zauważalny procent.
Kontrola nad tag managerem i zewnętrznymi skryptami
Tag manager to wygoda dla marketingu, ale często największy generator dodatkowych requestów. W panelu zwykle lądują kolejne kody śledzące, testowe piksele, stare eksperymenty A/B, które po kampanii nikt nie sprząta.
Dobry, budżetowy porządek w tag managerze:
- lista wszystkich tagów z opisem, do czego służą i kto jest za nie odpowiedzialny,
- weryfikacja, czy kampanie, pod które były zakładane, nadal działają,
- wyłączenie lub archiwizacja tych, które nie są aktywne,
- ustawienie reguł uruchamiania nie globalnie „na wszystkich stronach”, tylko na konkretnych widokach, gdzie tag jest faktycznie potrzebny.
Sprawdzając w DevTools zakładkę Network z filtrem „JS”, można łatwo zobaczyć, które pliki przychodzą z domen zewnętrznych (analityka, reklamy, heatmapy). Przy każdym z nich warto zadać jedno pytanie: czy ta integracja przynosi dziś realną wartość, czy jest tylko pozostałością po kampanii sprzed kilku miesięcy.
Mierzenie efektu zmian w JS na poziomie requestów
Żeby prace przy JS nie zamieniły się w nieskończoną refaktoryzację, przydaje się prosta metryka: liczba zapytań JS na kluczowych widokach przed i po zmianach, plus czas do pierwszej interakcji (FID/INP lub uproszczony „czas do kliknięcia”).
Prosty proces kontroli:
- Zapisać zrzut Network (z filtrem JS) dla strony głównej, listy produktów i karty produktu.
- Po wprowadzeniu jednej większej paczki zmian powtórzyć pomiary w tych samych warunkach.
- Jeśli liczba requestów spadła, ale strona reaguje wolniej, szukać winnego w kolejności ładowania, a nie w samej liczbie plików.
Chodzi o to, żeby każdy „quest” typu „odchudź JS” kończył się realną, zmierzoną różnicą, a nie tylko poczuciem, że kod wygląda ładniej w repozytorium.
Najważniejsze wnioski
- Nadmiar zapytań HTTP mocno podbija FCP i LCP – użytkownik dłużej widzi pusty ekran lub migotanie stylów, co skutkuje szybkim zamknięciem karty i ucieczką jeszcze przed pierwszym kliknięciem.
- Liczba requestów jest tak samo istotna jak łączna waga plików: wiele małych CSS/JS tworzy kolejkę i narzut sieciowy, nawet jeśli każdy plik z osobna jest lekki.
- Najlepszym kompromisem jest hybryda: mały krytyczny CSS inline + kilka plików ładowanych później oraz ograniczenie blokującego JavaScript (defer/async), zamiast jednego wielkiego pakietu lub dziesiątek mikroplików.
- Sklepy e-commerce generują znacznie więcej zapytań niż proste strony, bo każdy moduł (koszyk, rekomendacje, chat, trackingi, widżety) dorzuca własne CSS, JS, fonty i obrazki – w motywach premium część z nich ładuje się nawet wtedy, gdy nie jest używana.
- Duża liczba requestów realnie obniża konwersję i podnosi koszt pozyskania klienta: wolny pierwszy widok na liście produktów lub checkout’cie przepala budżet reklamowy na wizyty, które kończą się przed załadowaniem strony.
- Głęboka optymalizacja liczby zapytań ma sens przede wszystkim przy sklepach ze stałym ruchem i przyzwoitym TTFB; przy małym, testowym projekcie wystarczy szybkie „odchudzenie” – wywalenie zbędnych wtyczek, ciężkich widżetów i nadmiarowych trackingów.






