Renderowanie kodu HTML i interaktywności po stronie klienta

Renderowanie kodu HTML za pomocą JavaScriptu różni się od renderowania kodu HTML wysyłanego przez serwer. Może to mieć wpływ na wydajność. W tym przewodniku dowiesz się, czym się różnią, i co możesz zrobić, aby zachować wydajność renderowania witryny, zwłaszcza w przypadku interakcji.

Przetwarzanie i renderowanie kodu HTML to coś, co przeglądarki robią bardzo dobrze domyślnie w przypadku witryn, które korzystają z wbudowanej logiki nawigacji przeglądarki (czasem nazywanej „tradycyjnym wczytywaniem strony” lub „twardą nawigacją”). Takie witryny są czasami nazywane aplikacjami wielostronicowymi (MPA).

Deweloperzy mogą jednak obejść domyślne ustawienia przeglądarki, aby dostosować aplikację do swoich potrzeb. Dotyczy to na pewno witryn, które korzystają z schematu aplikacji jednostronicowej (SPA), która dynamicznie tworzy większość elementów HTML/DOM po stronie klienta za pomocą JavaScriptu. Renderowanie po stronie klienta to nazwa tego wzorca projektowania. Może ono mieć wpływ na czas interakcji do kolejnego wyrenderowania (INP) w witrynie, jeśli wymaga zbyt dużej ilości pracy.

Z tego przewodnika dowiesz się, jaka jest różnica między użyciem kodu HTML wysyłanego przez serwer do przeglądarki a jego utworzeniem po stronie klienta za pomocą JavaScriptu oraz jak to drugie może spowodować dużą latencję interakcji w krytycznych momentach.

sposób, w jaki przeglądarka renderuje kod HTML dostarczony przez serwer;

W przypadku tradycyjnego wczytywania stron podczas każdej operacji nawigacyjnej serwer wysyła kod HTML. Jeśli wpiszesz adres URL na pasku adresu przeglądarki lub klikniesz link w kampanii z zachętą do zakupu, nastąpi ta sekwencja działań:

  1. Przeglądarka wysyła żądanie nawigacji do podanego adresu URL.
  2. Serwer odpowiada kodami HTML w kawałkach.

Ostatni krok jest kluczowy. Jest to też jedna z najbardziej podstawowych optymalizacji wydajności w wymianie między serwerem a przeglądarką i jest znana jako streaming. Jeśli serwer może zacząć wysyłać kod HTML tak szybko, jak to możliwe, a przeglądarka nie czeka na otrzymanie całej odpowiedzi, może przetwarzać kod HTML w porcjach w miarę ich przychodzenia.

Zrzut ekranu przedstawiający parsowanie kodu HTML wysłanego przez serwer, zwizualizowane w panelu wydajności w Narzędziach deweloperskich w Chrome Gdy strumień danych HTML jest odbierany, jego fragmenty są przetwarzane w ramach wielu krótszych zadań, a renderowanie odbywa się stopniowo.
Skanowanie i renderowanie kodu HTML dostarczonego przez serwer, które są wizualizowane w panelu wydajności w Narzędziach deweloperskich w Chrome. Zadania związane z analizowaniem i renderowaniem kodu HTML są dzielone na fragmenty.

Podobnie jak większość działań w przeglądarce, parsowanie kodu HTML odbywa się w ramach zadań. Gdy kod HTML jest przesyłany strumieniowo z serwera do przeglądarki, przeglądarka optymalizuje jego analizę, wykonując ją po kawałku, ponieważ bity tego strumienia docierają w partiach. W konsekwencji po przetworzeniu każdego fragmentu przeglądarka okresowo przekazuje kontrolę wątkowi głównemu, co pozwala uniknąć długich zadań. Oznacza to, że podczas analizowania kodu HTML mogą być wykonywane inne czynności, w tym renderowanie stopniowe niezbędne do wyświetlenia strony użytkownikowi oraz przetwarzanie interakcji użytkownika, które mogą wystąpić w kluczowym okresie uruchamiania strony. Dzięki temu strona uzyska lepszy wynik interakcja do kolejnego wyrenderowania (INP).

Wniosek? Gdy przesyłasz strumień HTML z serwera, otrzymujesz bezpłatnie stopniowe analizowanie i renderowanie HTML oraz automatyczne oddawanie wątku głównemu. W przypadku renderowania po stronie klienta nie masz tego problemu.

Jak przeglądarka renderuje kod HTML dostarczony przez JavaScript

Każde żądanie nawigacyjne dotyczące strony wymaga od serwera dostarczenia pewnej ilości kodu HTML, ale niektóre witryny będą używać wzorca SPA. Takie podejście często polega na tym, że serwer dostarcza minimalny początkowy ładunek danych HTML, ale klient wypełnia główną część treści strony kodem HTML złożonym z danych pobranych z serwera. Kolejne przejścia – w tym przypadku nazywane „miękkimi przejściami” – są obsługiwane wyłącznie przez JavaScript, który wypełnia stronę nowym kodem HTML.

Renderowanie po stronie klienta może też występować w przypadku aplikacji innych niż SPA, ale tylko w ograniczonym zakresie, gdy kod HTML jest dynamicznie dodawany do DOM za pomocą JavaScriptu.

Istnieje kilka typowych sposobów tworzenia kodu HTML lub dodawania elementów do DOM za pomocą JavaScriptu:

  1. Właściwość innerHTML umożliwia ustawienie treści w dotychczasowym elemencie za pomocą ciągu znaków, który przeglądarka przeanalizuje w ramach DOM.
  2. Metoda document.createElement umożliwia tworzenie nowych elementów do dodania do DOM bez używania analizowania HTML w przeglądarce.
  3. Metoda document.write umożliwia zapisywanie kodu HTML w dokumencie (a przeglądarka interpretuje go tak samo jak w przypadku podejścia 1). Z kilku powodów nie zalecamy jednak korzystania z document.write.
Zrzut ekranu przedstawiający parsowanie kodu HTML renderowanego za pomocą JavaScriptu w panelu wydajności w Narzędziach deweloperskich w Chrome Praca odbywa się w jednym długim zadaniu, które blokuje wątek główny.
Przetwarzanie i renderowanie kodu HTML za pomocą JavaScriptu po stronie klienta, widoczne w panelu wydajności w Narzędziach deweloperskich w Chrome. Zadania związane z analizą i renderowaniem nie są dzielone na części, co powoduje, że długie zadanie blokuje wątek główny.

Tworzenie kodu HTML/DOM za pomocą JavaScriptu po stronie klienta może mieć poważne konsekwencje:

  • W przeciwieństwie do kodu HTML przesyłanego przez serwer w odpowiedzi na żądanie nawigacji zadania JavaScript na kliencie nie są automatycznie dzielone na części, co może spowodować, że długie zadania będą blokować wątek główny. Oznacza to, że jeśli na kliencie tworzysz zbyt dużo HTML-a/DOM-u, może to negatywnie wpłynąć na INP strony.
  • Jeśli kod HTML jest tworzony na kliencie podczas uruchamiania, zasoby do niego odwołujące się nie będą wykrywane przez skaner wstępnego wczytywania przeglądarki. Z pewnością będzie to miało negatywny wpływ na największe wyrenderowanie treści (LCP) strony. Chociaż nie jest to problem z wydajnością w czasie działania (jest to raczej problem z opóźnieniem sieci w pobieraniu ważnych zasobów), nie chcesz, aby pominięcie tej podstawowej optymalizacji wydajności przeglądarki miało wpływ na LCP Twojej witryny.

Co możesz zrobić, aby zminimalizować wpływ renderowania po stronie klienta na wydajność

Jeśli Twoja witryna jest silnie zależna od renderowania po stronie klienta, a w danych polowych obserwujesz niskie wartości INP, możesz się zastanawiać, czy renderowanie po stronie klienta ma coś wspólnego z problemem. Jeśli np. Twoja witryna jest aplikacją SPA, dane z pola mogą wskazywać na interakcje powodujące znaczne obciążenie renderowania.

Niezależnie od przyczyny, poniżej znajdziesz kilka potencjalnych przyczyn, które mogą pomóc Ci rozwiązać problem.

Przesyłaj z serwera jak najwięcej kodu HTML

Jak już wspomnieliśmy, przeglądarka domyślnie bardzo wydajnie obsługuje kod HTML z serwera. Rozbija analizę i renderowanie kodu HTML w sposób, który pozwala uniknąć długich zadań i zoptymalizować łączny czas działania wątku głównego. W efekcie powoduje to krótszy czas całkowitego blokowania (TBT), który wykazuje silną korelację z wartością INP.

Do tworzenia witryny możesz używać frameworku front-end. W takim przypadku musisz się upewnić, że renderujesz kod HTML komponentu na serwerze. Dzięki temu ograniczysz ilość początkowego renderowania po stronie klienta, jakiego wymaga Twoja witryna, co powinno poprawić komfort korzystania z niej.

  • W przypadku Reacta warto użyć interfejsu Server DOM API, aby renderować kod HTML na serwerze. Pamiętaj jednak, że tradycyjna metoda renderowania po stronie serwera wykorzystuje podejście synchroniczne, które może wydłużać czas do pierwszego bajtu (TTFB), a także kolejne dane, takie jak pierwsze wyrenderowanie treści (FCP) i LCP. W miarę możliwości używaj interfejsów przesyłania strumieniowego dla Node.js lub innych środowisk wykonawczych JavaScriptu, aby serwer mógł jak najszybciej rozpocząć przesyłanie strumieniowe kodu HTML do przeglądarki. Next.js to framework oparty na React, który domyślnie zawiera wiele sprawdzonych metod. Oprócz automatycznego renderowania kodu HTML na serwerze może też generować statyczny kod HTML dla stron, które nie zmieniają się w zależności od kontekstu użytkownika (np. uwierzytelniania).
  • Vue domyślnie wykonuje też renderowanie po stronie klienta. Jednak podobnie jak React, Vue może też renderować kod HTML komponentu na serwerze. W miarę możliwości korzystaj z tych interfejsów API po stronie serwera lub rozważ użycie abstekcji wyższego poziomu w projekcie Vue, aby ułatwić wdrażanie sprawdzonych metod.
  • Domyślnie Svelte renderuje HTML na serwerze, ale jeśli kod komponentu potrzebuje dostępu do przestrzeni nazw specyficznej dla przeglądarki (na przykład window), możesz nie mieć możliwości renderowania kodu HTML tego komponentu na serwerze. W miarę możliwości stosuj alternatywne podejścia, aby nie powodować zbędnego renderowania po stronie klienta. SvelteKit, który jest dla Svelte tym, czym Next.js jest dla Reacta, wdraża w projektach Svelte jak najwięcej sprawdzonych metod, aby uniknąć potencjalnych pułapek w projektach, które korzystają tylko ze Svelte.

Ogranicz liczbę węzłów DOM tworzonych po stronie klienta.

Gdy DOM-y są duże, przetwarzanie ich wymaga więcej czasu. Niezależnie od tego, czy Twoja witryna jest pełną aplikacją SPA, czy wstrzykuje nowe węzły do istniejącego DOM-u w ramach interakcji w ramach MPA, rozważ zachowanie tych DOM-ów na możliwie najmniejszym poziomie. Pomoże to zmniejszyć nakład pracy wymaganej podczas renderowania po stronie klienta w celu wyświetlenia kodu HTML, co powinno zmniejszyć INP witryny.

Rozważ architekturę skryptu service worker w usługach strumieniowania

Jest to zaawansowana technika, która może nie działać w każdym przypadku, ale może sprawić, że Twoja witryna MPA będzie się wczytywać błyskawicznie, gdy użytkownicy będą przechodzić z jednej strony na drugą. Możesz użyć skryptu service worker, aby wstępnie przechwycić statyczne części witryny w CacheStorage, a za pomocą interfejsu ReadableStream API pobrać z serwera pozostałą część kodu HTML strony.

Gdy użyjesz tej techniki, nie będziesz tworzyć kodu HTML po stronie klienta, ale błyskawiczne wczytywanie częściowych treści z pamięci podręcznej sprawi, że Twoja witryna będzie się ładować szybko. Witryny korzystające z tego podejścia mogą działać prawie jak SPA, ale bez wad renderowania po stronie klienta. Zmniejsz też ilość kodu HTML żądanego od serwera.

Krótko mówiąc, architektura serwisu przesyłania strumieniowego nie zastępuje wbudowanej logiki nawigacji przeglądarki, lecz ją uzupełnia. Więcej informacji o tym, jak to zrobić za pomocą Workboxa, znajdziesz w artykule Szybsze aplikacje wielostronicowe dzięki strumieniom.

Podsumowanie

Sposób odbierania i renderowania kodu HTML przez witrynę ma wpływ na jej wydajność. Gdy serwer wysyła wszystkie (lub większość) tagi HTML wymagane do działania witryny, otrzymujesz wiele bezpłatnie: stopniowe analizowanie i renderowanie oraz automatyczne oddawanie wątku głównemu, aby uniknąć długich zadań.

Wyświetlanie HTML-a po stronie klienta powoduje wiele potencjalnych problemów z wydajnością, których w wielu przypadkach można uniknąć. Ze względu na wymagania poszczególnych witryn nie zawsze jest to jednak możliwe. Aby ograniczyć czas wykonywania zadań, które mogą być spowodowane nadmiernym renderowaniem na stronie klienta, wyślij jak najwięcej kodu HTML witryny z serwera, gdy tylko to możliwe, utrzymuj jak najmniejsze rozmiary DOM dla kodu HTML, który musi być renderowany na kliencie, i rozważ alternatywne architektury, aby przyspieszyć dostarczanie kodu HTML do klienta, korzystając jednocześnie z dodatkowego analizowania i renderowania, które przeglądarka wykonuje w przypadku kodu HTML wczytanego z serwera.

Jeśli uda Ci się zminimalizować renderowanie po stronie klienta, poprawisz nie tylko INP witryny, ale też inne wskaźniki, takie jak LCP, TBT, a w niektórych przypadkach nawet TTFB.

Baner powitalny z Unsplash autorstwa Maik Jonietz.