
Weblow - jak zoptymalizować wydajność frontendu względem Core Web Vitals?
Chciałbym przedstawić Ci przykład typowej strony stworzonej za pośrednictwem narzędzia Webflow. Na podstawie wielu podobnych witryn, zauważam szereg powtarzających się problemów wydajności w tego rodzaju projektach. Omówię je krótko, a następne wskażę konkretne rozwiązania w kodzie.
Niniejszy ebook traktuje o stronie pod testowym adresem: https://sec-alliance-dev-ac6f5527d165f7e38c4890.webflow.io/
Witryna istnieje pod innym, produkcyjnym adresem (custom domain). Według danych wydajności CrUX, użytkownicy tej strony mierzą się z nienajlepszym doświadczeniem (UX), zwłaszcza pod kątem szybkości jej ładowania. Wskazują na to metryki FCP oraz LCP (dane od użytkowników mobile - narzędzie treo.sh):

Bardziej szczegółowe dane o kondycji całej strony (wskaźniki odnośnie całej domeny) polecam prześledzić w swoich projektach za pośrednictwem narzędzia CrUX Vis: https://cruxvis.withgoogle.com/.
Dokonałem również testów wydajności za pośrednictwem narzędzia WebPageTest (4G, mobile, Frankfurt - przykład rezultatu testów: https://www.webpagetest.org/result/250530_BiDcXK_BYE/3/details/):

Strona rozpoczyna proces renderowania BARDZO późno. W testach syntetycznych, podczas pierwszej wizyty, niejednokrotnie trzeba czekać ponad 8 sekund (!), aby cokolwiek się wyrenderowało. W tym samym czasie pojawia się również największy element LCP, co może wskazywać jednoznacznie, że cały proces renderowania jest mocno przyblokowany. Użytkownik przez szereg sekund nie widzi nic i dopiero po długim czasie widzi całą stronę. Bardzo niekorzystny efekt.
Według innych metryk - jak np. współczynnik odrzuceń - ponad 75% użytkowników tej strony rezygnuje z wizyty jeszcze zanim strona się w pełni załaduje. Kto wie - być może nawet i więcej, gdyż skrypt Google Analytics ładowany jest nawet po czasie dłuższym niż 8 sekund.
Strzelam w ciemno, że wielu użytkowników nie czeka nawet na wyrenderowanie pierwszych elementów na stronie i już odchodzi. Gdzie indziej. Do konkurencji. Mając od razu wyrobione zdanie o stronie, a potencjalnie i o całej firmie. Jest bardzo wolna!
Analiza ładowania & renderowania
W analizie szybkości ładowania i renderowania strony niezwykle istotne jest, aby prześledzić krytyczną ścieżkę renderowania. Czym ona jest, jak działa przeglądarka i na co powinniśmy zwrócić uwagę omawiam w jednej z lekcji mojego szkolenia Zoptymalizowany Frontend, którą możesz sobie zobaczyć za darmo na moim kanale YouTube:
W skrócie: kiedy wiemy, że na naszej stronie jest problem z krytyczną ścieżką renderowania, uruchamiany test syntetyczny (polecam narzędzie webpagetest.org) i analizujemy tzw. waterfall - wodospad ładowanych zasobów. Plik po pliku, zasób po zasobie - wszystko, co jest jest ładowane przed zieloną linią, która wskazuje na moment rozpoczęcia renderowania frontendu:

CSS
Zacznijmy od CSS. Podczas analizy wodospadu ładowanych plików na stronie widzimy blokujący renderowanie arkusz stylów.
W momencie, kiedy tworzę tego e-booka, koincydencyjnie Webflow ogłosiło nową funkcjonalność o nazwie „Per page CSS” (https://webflow.com/updates/per-page-css). Pozwala to na ładowanie potencjalnie mniejszego rozmiaru kodu CSS, gdyż ładowany jest tylko ten CSS, który faktycznie jest wykorzystywany na danej stronie.

Nie czekając długo, upewniłem się, że ów feature istnieje w moim koncie Webflow, następnie włączyłem go i przeprowadziłem kolejne testy syntetyczne strony. Poniżej rezultaty.
PRZED (ładowany jest 1 arkusz CSS):

PO (ładowane są 2 arkusze CSS - oba blokujące renderowanie):

Po włączeniu opcji „Per page CSS”, moment startu renderowania wydłużył nam się jeszcze bardziej, aż o 0.7s! Webflow wygenerował 1 główny arkusz CSS o nazwie „shared.min.css” (czyli wykorzystywane style globalnie, na każdej podstronie), a następnie dedykowany danej stronie inny arkusz (w sumie to arkusze - im więcej mamy typów podstron/layoutów, tym więcej tych plików będzie).
Dlaczego strona renderuje się WOLNIEJ?
Dlatego, bo ładowanie kilku, mniejszych plików CSS w tym samym czasie, jest wolniejsze niż załadowanie jednego pliku CSS. Nawet, jeśli sumaryczna waga pojedynczo ładowanych plików CSS na danej stronie jest mniejsza. Dlatego, bo mamy kolejny, dodatkowy request w naszym wodospadzie żądań. Polecam Twojej uwadze ten artykuł: https://www.debugbear.com/blog/make-fewer-http-requests.
Porady optymalizacyjne:
- Ładuj 1 plik CSS na stronach Webflow.
- Blokująca renderowanie charakterystyka ładowanego pliku CSS to typowa przypadłość stron Webflow. Walka z blokującym renderowanie zasobem może się odbyć np. poprzez 103 Early Hint - czyli metodę ładowania dokumentu HTML i pliku CSS w tym samym czasie. Możesz o tym przeczytać u mnie na blogu: https://www.webdevinsider.pl/blog/koniec-http2-server-push-czas-na-103-early-hints
Jak to zastosować w praktyce? O tym w ostatnim rozdziale tego ebook’a. - Staraj się nie tworzyć zbyt wielu osobnych klas CSS w Webflow. Zależy nam na jak największej reużywalności klas, a przy tym na jak najmniejszym rozmiarze CSS’a. To, jak tworzymy/projektujemy strony w Webflow bezpośrednio przekłada się na rozmiar wygenerowanych zasobów, jak wspomniany CSS.
Fonty
Na omawianej stronie ładowane są fonty za pośrednictwem Google Fonts oraz inne, hostowane lokalnie.

Te, które ładowane są przez Google Fonts są szczególnie złe. Dlaczego? Nie dość, że ładujemy pliki fontów za pośrednictwem wcześniej załadowanego arkusza CSS z zewnętrznej domeny Google Fonts (co już samo w sobie opóźnia moment ich załadowania znacząco), to dodatkowo Webflow korzysta z optymalizacji renderowania fontów o nazwie Web Font Loader. Pech chciał, że skrypt JS ładowany jest znowu z zewnętrznej domeny, a dodatkowo synchronicznie, blokując parsowanie HTML i tym samym renderowanie strony. To generuje kolejne duże opóźnienie w procesie startu renderowania naszej strony.

Porady optymalizacyjne:
- Ładuj tylko self-hostowane pliki fontów. Zrezygnuj z Google Fonts lub Typekit. Jeśli korzystasz z fontów Google’a - po prostu pobierz je, a następnie zoptymalizuj.
- Dokonaj optymalizacji ładowanych plików (idealnie w formacie .woff2, gdyż zapewnia najlepszą kompresję pliku). Polecane narzędzie do optymalizacji fontów: https://transfonter.org/
- Dodaj font-display: swap, aby zapewnić widoczność tekstu na stronie tak szybko, jak to możliwe, nie czekając na załadowanie plików fontów.
- Dobierz font fallbackowy, domyślny, systemowy, który rozmiarem i typem fontu będzie naje najbardziej zbliżony do customowego, docelowego, minimalizując negatywny efekt FOUT i przesunięcia układu CLS.
Co zoptymalizowałem?
Pobrałem font z Google Fonts i dokonałem optymalizacji względem listy powyżej. Przeprowadziłem kolejny test wydajności na WebPageTest i oto wyniki: https://www.webpagetest.org/result/250530_BiDcJ3_D3F/3/details/
Na chwilę obecną możemy liczyć na szybsze renderowanie tyko o 0.1s. Niewiele, ale zapewniam Cię, że każda prawidłowa optymalizacja przyniesie nam pod koniec efekt kuli śniegowej względem wydajności. Cierpliwości :)

Na wodospadzie żądań nie ma tym samym blokujących renderowanie zasobów JS, a pliki fontów ładowane są z tej samej domeny. Na razie dość późno, ale sytuacja może się zmienić w miarę kolejnych optymalizacji.

Audyt <head>
Dokonałem audytu sekcji <head> za pośrednictwem narzędzia capo.js: https://rviscomi.github.io/capo.js/. Wspaniale wskazuje nam na problematyczne skrypty, arkusze CSS czy też nieprawidłową kolejność tagów.
Tym oto sposobem udało się dotrzeć np. do takiego fragmentu kodu - odpowiedzialnego za komponent CMP (Cookie Consent Manager):

Powoduje on blokowanie renderowania przez arkusz CSS (dodatkowo hostowany na zewnętrznej domenie, co jeszcze bardziej pogarsza sprawę).
Co zoptymalizowałem?
- Postanowiłem ładować CSS asynchronicznie, nie blokując renderowania strony.
- Przez JS sprawdzam, kiedy CSS jest załadowany i wtedy inicjalizuję skrypt JS odpowiedzialny za Cookie Consent Manager.
- Zależy nam jednocześnie na szybkim pobraniu tego pliku CSS, więc dodałem preload.
Gotowy kod:

Rezultat:

GIGANTYCZNA różnica. Z ponad 8 sekund udało się zejść do 3.5s., jeśli chodzi o start renderowania. Ale to nie koniec - działamy dalej :)
JS
Podczas analizy ładowanych zasobów warto sobie podzielić te skrypty na te, które są ładowane lokalnie oraz te „z zewnątrz”.
W podanym projekcie mamy do czynienia ze skryptem Hubspot odpowiedzialnym za formularze:

Po krótkiej analizie kodu HTML okazało się, że skrypt ten odpowiada za formularze, ale które widoczne są dla użytkownika dopiero, gdy kliknie w dany przycisk i wyświetli się popup. Drugi przypadek to sekcje „below the fold”, do których użytkownik musi dopiero dotrzeć, scrollując w dół strony.
Wniosek? Nie ma sensu ładować skryptu Hubspot od samego początku podczas wizyty na stronie. Warto zastosować wzorzec ładowania zasobów o nazwie „Load on interaction”. Tą interakcją może być chociażby zdarzenie scroll albo click.
Jak to wdrożyć? Na przykład przez zastosowanie autorskiego rozwiązania <fscript>: https://github.com/biggerpicturestudio/delaying-3rd-parties
Jeśli chodzi o fragment kodu na tej stronie:

…dokonałem następującej modyfikacji:

Stworzyłem dedykowaną metodę loadHubspotScript, którą umieściłem w <head> strony.
Rezultat:

Obrazki - skrótowo
- Zmieniłem sposób osadzenia obrazków jako tło w CSS na obrazki <img> bezpośrednio w dokumencie HTML, wraz z atrybutem loading=łazy, dzięki czemu obrazki ładują się teraz na żądanie, dopiero gdy scrollujemy do odpowiednich sekcji, w których występują.
- Skompresowałem obrazki i uczyniłem je responsywnymi.

Porządkowanie kodu JS - skrótowo
- Przeniosłem snippet kodu Google Tag Manager na początku sekcji <head> w Webflow.
- Dodałem „rozgrzanie” połączenia do domen Google Tag Manager oraz Google Analytics poprzez preconnect.
- Dodałem atrybut async do skryptu Hubspot.

Typowa strona Webflow w większości przypadków boryka się z tymi samymi problemami:
- późne rozpoczęcie renderowania
- blokujące skrypty JS
- blokujące arkusze CSS
- nieodpowiednia priorytetyzacja ładowania plików
- niezoptymalizowane ładowanie fontów
Wszystkie omówione przeze mnie poprawki wydajności przyczyniły się do osiągnięcia następującego rezultatu:

Strona ładuje i renderuje się ok. cztery razy szybciej!
Dalsze optymalizacje (np. automatyczny preload CSS'a przez Early Hints)
Na początku tego bloga wspomniałem o sposobach poradzenia sobie np. z blokującym renderowanie plikiem CSS za pośrednictwem Early Hints.
Jak tego dokonać? Najprościej poprzez Cloudflare.
Pełny opis wraz z kodem źródłowym to wykorzystania na stronie (jakiejkolwiek, nie tylko Webflow) znajdziesz w moim darmowym ebooku do pobrania poniżej.