Optymalizacja krytycznej ścieżki renderowania

Szybkość wczytywania strony internetowej ma ogromne znaczenie dla odbiorcy naszych treści. Osobiście odczuwam duży dyskomfort korzystając z serwisów które wczytują się wolno. Doskonały opis problemu przedstawia Google na swojej oficjalnej stronie. W tym artykule nie będę ponownie omawiał źródłeł pochodzenia tych problemów, lecz skupię się na praktycznych poradach, które pozytywnie wpływają na szybkość wczytywania stron. Warto zaznaczyć, że nie zawsze wprawdzenie tych zmian jest proste. Najwięcej zyskamy, kiedy o optymalizacji pomyślimy już na etapie projektowania strony.

Optymalizacja Treści

Zanim przejdziemy do konkretów, kila słów na temat optymalizacji treści na stronie. Przed wprowadzeniem poprawek technicznych w kodzie naszej strony, zastanówmy się czy aby na pewno wszyskie wczytywane przez nas zasoby są odpowiednio zoptymalizowane i konieczne.

Na tym etapie warto zastawnowić się czy nie wysyłamy użytkownikowi zbędnych obrazów, stylów CSS, skryptów JS, czy kodu HTML. Piszę o tym, bo zdarza się to często, a największy zysk osiągniemy, po prosu eliminując takie zasoby.

Do tego punktu zaliczamy także:

  • kompresję danych (GZIP)
  • minifikację kodu HTML, CSS, JS
  • optymalizację obrazów
  • optymalizację czcionek
  • Buforowanie HTTP

Zadbanie o prawidłową optymalizację powyższych obszarów jest podstawową kwestią, o którą powinniśmy zadbać i z pewnością przyspieszy to działanie naszej strony. Ale to nie wszystko co możemy zrobić.

Optymalizacja Krytycznej Ścieżki Renderowania

To czy nasza strona posiada zasoby blokujące renderowanie strony możemy zobaczyć np. za pomocą narzędzia Pagespeed. Zazwyczaj winowajcami są akrusze stylów, ale mogą to być również skrypty JS oraz czcionki.

Wyzwanie przed jakim stoi przeglądarka, to pobranie i interpretacja zasobów, w celu wyświetlenia treści w pożądany przez nas sposób. Proces ten odbywa się w kilku krokach i dopiero po jego zakończeniu strona zostanie wyświetlona. Z punktu widzenia wygody użytkownika, który chce jak najszybciej dotrzeć do treści, stanowi to oczywiście przeszkodę. Jako osoby odpowiedzialne, za ten ważny aspekt życia odbiorcy, powinniśmy poświęcić temu zagadnieniu nieco uwagi.

Style CSS Blokujące Renderowanie

Zewnętrze arkusze CSS są zasobem, który domyślnie blokuje renderowanie strony. Wynika to bezpośrednio z mechanizmu działania przeglądarki, która wstrzymuje renderowanie treści, w celu utworzenia tzw. drzewa renderowania. Dopiero po jego utworzeniu przeglądarka będzie “wiedziała” jak wyświetlić naszą stronę. Warto w tym momencie zaznaczyć, że sposób w jaki piszemy reguły CSS, wpływa na czas trwania tego procesu. W celu jego skrócenia warto stosować możliwie najprostsze reguły CSS opisujące wygląd strony. Jednym z przykładów podejścia pozwalającego na optymalizację czasu tworzenia drzewa jest metodologia BEM.

Rozwiązaniem powyższego problemu jest jak naszybsze przesłanie kodu CSS użykownikowi, w celu skrócenia czasu renderowania. Dla arkuszy stylów nieprzekraczających kilkuset linii możemy to osiągnąć poprzez umieszczenie kodu CSS bezpośrednio w nagłówku strony wewnątrz znacznika style.

Niestety, jak to w życiu, nie wszystko jest takie proste. Zazwyczaj mamy do czynienia z kodem CSS, który przekracza rozmiarem kilka tysięcy linii. Co w takim przypadku?

Wyodrębnienie Krytycznego Kodu CSS

Jednym z pomysłów na skrócenie czasu renderowania w przypadku bardzo dużych arkuszy stylów, jest wyodrębnienie krytycznego kodu CSS, który jest niezbędny przeglądarce do pierwszego wyrenderowania strony. Innymi słowy, w nagłówku strony umieszczamy wyłącznie style potrzebne do wyświetlenia części strony widocznej na ekranie.

Jeżeli zaczynamy nowy projekt, wówczas, obok głównego arkusza stylów, możemy utworzyć osobny, zwierający wyłącznie krytyczny kod CSS, którego zawartość ostatecznie umieszczamy w nagłówku wewnątrz znacznika style.

W sytuacji, gdy mamy do czynienia z już istniejącą stroną, z pomocą przychodzą narzędzia takie jak Critical-path CSS, które pozwalają na wyodrębnienie krytycznego kodu CSS, z już istniejących stylów.

Asynchroniczne Wczytywanie Arkuszy Stylów

Jeżeli poprawnie wyodrębniłeś style krytyczne, to po ich “wstrzyknięciu” do nagłówka, strona po załadowaniu powinna zawierać style wyłącznie dla części widocznej na ekranie. Pozostaje kwestia załadowania pozostałej części kodu CSS, w sposób, który nie będzie blokował renderowania. Możemy takie rozwiązanie zaimplementować sami, jednak w świecie modularnego JavaScriptu, prawie wszystko mamy podane na tacy. Dlatego po raz kolejny odwołam się do już istniejącego narzędzia, którym jest loadCSS. Narzędzie to zostało stworzone właśnie w celu asynchronicznego ładowania kodu CSS, który nie jest krytyczny dla pierwszego renderowania strony.

W pierwszej kolejności autorzy zalecają skorzystanie ze sposobu, który jest zaimplementowany w nowszych przeglądarkach, a mianowicie skorzystanie z tagu link z atrybutem preload, który pozwala na asynchroniczne pobranie zasobu. Przykładowy kod do umieszczenia w nagłówku strony, wygląda następująco:

<link rel="preload" href="path/to/style.css" as="style">

Podczas ładowania strony, plik zostanie pobrany “w tle”. Jednak na tym kończy się rola tego tagu, o poprawne dołączenie pobranego pliku musimy zadbać sami:

<link rel="preload" href="path/to/style.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="path/to/style.css"></noscript>

Na koniec zadbajmy również o prawidłowe wczytywanie arkusza stylów w przeglądarkach nieobsługujących tej metody. W tym celu posłużymy się wyżej wymienionym skryptem loadCSS oraz polyfillem dla atrybutu rel=”preload”:

<script>
/*! loadCSS. [c]2017 Filament Group, Inc. MIT License */
!function(a){"use strict";var b=function(b,c,d){function j(a){if(e.body)return a();setTimeout(function(){j(a)})}function l(){f.addEventListener&&f.removeEventListener("load",l),f.media=d||"all"}var g,e=a.document,f=e.createElement("link");if(c)g=c;else{var h=(e.body||e.getElementsByTagName("head")[0]).childNodes;g=h[h.length-1]}var i=e.styleSheets;f.rel="stylesheet",f.href=b,f.media="only x",j(function(){g.parentNode.insertBefore(f,c?g:g.nextSibling)});var k=function(a){for(var b=f.href,c=i.length;c--;)if(i[c].href===b)return a();setTimeout(function(){k(a)})};return f.addEventListener&&f.addEventListener("load",l),f.onloadcssdefined=k,k(l),f};"undefined"!=typeof exports?exports.loadCSS=b:a.loadCSS=b}("undefined"!=typeof global?global:this);
/*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */
!function(a){if(a.loadCSS){var b=loadCSS.relpreload={};if(b.support=function(){try{return a.document.createElement("link").relList.supports("preload")}catch(a){return!1}},b.poly=function(){for(var b=a.document.getElementsByTagName("link"),c=0;c<b.length;c++){var d=b[c];"preload"===d.rel&&"style"===d.getAttribute("as")&&(a.loadCSS(d.href,d,d.getAttribute("media")),d.rel=null)}},!b.support()){b.poly();var c=a.setInterval(b.poly,300);a.addEventListener&&a.addEventListener("load",function(){b.poly(),a.clearInterval(c)}),a.attachEvent&&a.attachEvent("onload",function(){a.clearInterval(c)})}}}(this);
</script>