Renderowanie kodu HTML i interaktywności po stronie klienta

Renderowanie HTML za pomocą JavaScriptu różni się od renderowania kodu HTML wysyłanego przez serwer, co może wpływać na wydajność. Przeczytaj ten przewodnik i dowiedz się, na czym polega różnica, i dowiedz się, jak możesz utrzymać wydajność renderowania witryny, zwłaszcza w przypadku interakcji.

Analiza i renderowanie kodu HTML jest obsługiwane przez przeglądarki bardzo dobrze w przypadku witryn korzystających z wbudowanej logiki nawigacyjnej – czasami nazywanej „tradycyjnym wczytywaniem stron” lub „utrudnianiem nawigacji”. Takie witryny są czasami nazywane aplikacjami wielostronicowymi (MPA).

Programiści mogą jednak obchodzić domyślne ustawienia przeglądarki, aby dostosować je do swoich potrzeb. Dotyczy to z pewnością witryn używających wzorca aplikacji jednostronicowej (SPA), który dynamicznie tworzy u klienta duże części kodu HTML/DOM za pomocą JavaScriptu. Renderowanie po stronie klienta to nazwa tego wzorca projektowego. Może ono mieć wpływ na interakcje z kolejnym wyrenderowaniem (INP) w witrynie, jeśli ilość pracy jest zbyt duża.

Ten przewodnik pomoże Ci porównać różnicę między używaniem kodu HTML wysłanego przez serwer do przeglądarki a tworzeniem go po stronie klienta za pomocą języka JavaScript oraz dlaczego ten drugi kod może skutkować dużymi opóźnieniami interakcji w kluczowych momentach.

Jak przeglądarka renderuje kod HTML dostarczony przez serwer

Wzorzec nawigacji używany podczas tradycyjnego wczytywania stron obejmuje odbieranie kodu HTML z serwera przy każdym nawigowaniu. Jeśli wpiszesz adres URL w pasku adresu przeglądarki lub klikniesz link w MPA, wystąpi taka seria zdarzeń:

  1. Przeglądarka wysyła żądanie nawigacji dla podanego adresu URL.
  2. Serwer wysyła odpowiedzi fragmentami kodu HTML.

Ostatni krok ma kluczowe znaczenie. To również jedna z najważniejszych metod optymalizacji wydajności w ramach wymiany serwer/przeglądarki i nazywana jest strumieniem strumieniowym. Jeśli serwer może rozpocząć wysyłanie kodu HTML tak szybko, jak to możliwe, a przeglądarka nie czeka na odpowiedź z całą odpowiedzią, może przetwarzać kod HTML w kolejnych porcjach.

Zrzut ekranu przedstawiający analizę kodu HTML wysłanego przez serwer w panelu wydajności Narzędzi deweloperskich w Chrome. W miarę napływu kodu HTML jego fragmenty są przetwarzane w ramach wielu krótszych zadań, a renderowanie przyrostowe.
Analiza i renderowanie kodu HTML dostarczone przez serwer, jak pokazano na panelu wydajności Narzędzi deweloperskich w Chrome. Zadania związane z analizowaniem i renderowaniem kodu HTML są dzielone na fragmenty.

Podobnie jak w przypadku większości przeglądarek, analizowanie kodu HTML odbywa się w ramach zadań. Gdy kod HTML jest przesyłany z serwera do przeglądarki, przeglądarka optymalizuje go, robiąc to krok po kroku, w miarę jak fragmenty przesyłanego strumienia są przesyłane we fragmentach. W efekcie przeglądarka okresowo wyświetla wątek główny po przetworzeniu każdego jego fragmentu, co pozwala uniknąć długich zadań. Oznacza to, że podczas analizy kodu HTML mogą być wykonywane inne operacje, w tym przyrostowe renderowanie niezbędne do przedstawienia strony użytkownikowi lub przetwarzanie interakcji użytkownika, które mogą mieć miejsce w kluczowym okresie uruchamiania strony. Takie podejście przekłada się na lepszy wynik interakcji z następnym wyrenderowaniem (INP) na stronie.

Wnioski? Gdy przesyłasz strumieniowo kod HTML z serwera, masz możliwość bezpłatnego analizowania i renderowania kodu HTML oraz automatycznego przekazywania go do wątku głównego. Jest to niemożliwe w przypadku renderowania po stronie klienta.

Jak przeglądarka renderuje kod HTML dostarczony przez JavaScript

Każde żądanie nawigacji do strony wymaga dostarczenia przez serwer pewnej ilości kodu HTML, ale niektóre witryny korzystają ze wzorca SPA. Ta metoda często polega na dostarczaniu przez serwer minimalnego ładunku początkowego kodu HTML, ale następnie klient wypełnia główny obszar treści strony kodem HTML pobranym z danych pobranych z serwera. Kolejne elementy nawigacyjne, czasem nazywane w tym przypadku „pozornym nawigacją”, są obsługiwane wyłącznie przez JavaScript, aby wypełnić stronę nowym kodem HTML.

Renderowanie po stronie klienta może się też odbywać w innych obszarach niż w rzadkich przypadkach, gdy kod HTML jest dynamicznie dodawany do DOM za pomocą JavaScriptu.

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

  1. Właściwość innerHTML umożliwia ustawienie treści w istniejącym elemencie za pomocą ciągu znaków, który przeglądarka analizuje za pomocą modelu DOM.
  2. Metoda document.createElement pozwala tworzyć nowe elementy dodawane do modelu DOM bez użycia analizy HTML w przeglądarce.
  3. Metoda document.write pozwala zapisać kod HTML w dokumencie (który jest następnie analizowany przez przeglądarkę jak w przypadku metody nr 1). Z wielu powodów zdecydowanie odradzamy jednak korzystanie z usługi document.write.
Zrzut ekranu z analizą kodu HTML wyrenderowanego za pomocą JavaScriptu wizualizowany w panelu wydajności Narzędzi deweloperskich w Chrome. Praca jest wykonywana w ramach pojedynczego, długiego zadania, które blokuje wątek główny.
Analiza i renderowanie kodu HTML z wykorzystaniem JavaScriptu na kliencie, tak jak pokazano to w panelu wydajności Narzędzi deweloperskich w Chrome. Zadania związane z jej analizą i renderowaniem nie są dzielone na części, co powoduje długie zadanie blokujące wątek główny.

Konsekwencje tworzenia kodu HTML/DOM za pomocą JavaScriptu po stronie klienta mogą być znaczące:

  • W przeciwieństwie do kodu HTML przesyłanego strumieniowo przez serwer w odpowiedzi na żądanie nawigacji zadania JavaScript na kliencie nie są automatycznie dzielone na części, co może skutkować długimi zadaniami blokującymi wątek główny. Oznacza to, że tworzenie zbyt dużej ilości kodu HTML/DOM naraz w kliencie może negatywnie wpłynąć na wartość INP strony.
  • Jeśli podczas uruchamiania klienta kod HTML zostanie utworzony, zasoby, do których odwołuje się klient, nie zostaną wykryte przez skaner wstępnego wczytywania przeglądarki. Będzie to z pewnością mieć negatywny wpływ na największe wyrenderowanie treści (LCP). Chociaż nie jest to problem z wydajnością działania środowiska wykonawczego (a raczej jest to problem z opóźnieniem sieci podczas pobierania ważnych zasobów), nie chcesz, aby pomijanie tej kluczowej optymalizacji wydajności przeglądarki miało wpływ na LCP Twojej witryny.

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

Jeśli Twoja witryna w dużym stopniu zależy od renderowania po stronie klienta, a w danych pól występują niskie wartości INP, możesz się zastanawiać, czy problem może mieć związek z renderowaniem po stronie klienta. Jeśli na przykład Twoja witryna to SPA, dane z pola mogą ujawnić interakcje odpowiedzialne za dużo pracy z renderowaniem.

Poniżej znajdziesz kilka potencjalnych przyczyn, które mogą pomóc Ci rozwiązać problem.

Prześlij jak najwięcej kodu HTML z serwera.

Jak już wspomnieliśmy, domyślnie przeglądarka obsługuje kod HTML z serwera w bardzo wydajny sposób. Podzieli ona analizowanie i renderowanie kodu HTML w sposób, który pozwala uniknąć długich zadań i optymalizuje łączny czas trwania wątku głównego. Prowadzi to do krótszego całkowitego czasu blokowania (TBT), a TBT jest silnie skorelowana z INP.

Do tworzenia witryny możesz polegać na platformie frontendowej. Jeśli tak, upewnij się, że renderujesz komponent HTML na serwerze. Ograniczy to ilość początkowego renderowania Twojej witryny po stronie klienta i powinno poprawić jej jakość.

  • W przypadku React trzeba będzie renderować kod HTML na serwerze za pomocą interfejsu Server DOM API. Pamiętaj jednak, że tradycyjna metoda renderowania po stronie serwera korzysta z metody synchronicznej, co może prowadzić do wydłużenia czasu do pierwszego bajtu (TTFB), a także z późniejszych danych, takich jak pierwsze wyrenderowanie treści (FCP) i LCP. Gdy to możliwe, używaj interfejsów API strumieniowania na potrzeby Node.js lub innych środowisk wykonawczych JavaScript, aby serwer mógł jak najszybciej rozpocząć strumieniowe przesyłanie kodu HTML do przeglądarki. Next.js – platforma oparta na Reactach – domyślnie zapewnia wiele sprawdzonych metod. Oprócz automatycznego renderowania kodu HTML na serwerze może on także statycznie generować 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. Vue, podobnie jak React, może jednak renderować komponent HTML na serwerze. W miarę możliwości wykorzystaj te interfejsy API po stronie serwera lub zastosuj w projekcie Vue wyższy poziom abstrakcji, aby ułatwić wdrożenie sprawdzonych metod.
  • Svelte domyślnie renderuje kod HTML na serwerze. Jeśli jednak kod komponentu wymaga dostępu do przestrzeni nazw dostępnej tylko w przeglądarce (na przykład window), wyrenderowanie kodu HTML tego komponentu na serwerze może być niemożliwe. Wszędzie tam, gdzie to możliwe, warto rozważyć alternatywne podejścia, które pozwolą uniknąć niepotrzebnego renderowania po stronie klienta. Pakiet SvelteKit, który jest Svelte tak, jak Next.js ma React, pozwala w miarę możliwości stosować w projektach Svelte wiele sprawdzonych metod, dzięki czemu można uniknąć potencjalnych problemów w projektach opartych tylko na Svelte.

Ogranicz liczbę węzłów DOM tworzonych na kliencie

Jeśli modele DOM są duże, ilość przetwarzana do ich wyrenderowania zwykle wzrasta. Niezależnie od tego, czy Twoja witryna jest pełnoprawną SPA, czy wstrzykuje nowe węzły do istniejącego DOM w wyniku interakcji z MPA, staraj się, by te DOM były jak najmniejsze. To pozwoli ograniczyć nakład pracy potrzebny do renderowania po stronie klienta przy wyświetlaniu kodu HTML, a tym samym obniżyć wartość INP witryny.

Rozważ architekturę instancji roboczej usługi strumieniowania

To zaawansowana technika, która może nie sprawdzić się w niektórych sytuacjach, ale pozwala przekształcić MPA w witrynę, która wczytuje się natychmiast, gdy użytkownicy przechodzą z jednej strony na następną. Możesz użyć skryptu service worker, by wstępnie buforować statyczne części witryny w CacheStorage, a jednocześnie używać interfejsu API ReadableStream do pobierania z serwera reszty kodu HTML strony.

Po zastosowaniu tej metody nie tworzysz kodu HTML po stronie klienta, ale natychmiastowe wczytywanie fragmentów treści z pamięci podręcznej daje wrażenie, że witryna wczytuje się szybko. Strony, które korzystają z tego podejścia, mogą wydawać się prawie jak aplikacje SPA, ale bez wad renderowania po stronie klienta. Pozwala to też zmniejszyć ilość kodu HTML żądanego z serwera.

Krótko mówiąc, architektura skryptu service worker nie zastępuje wbudowanej w przeglądarkę logiki nawigacyjnej – tylko ją dodaje. Więcej informacji, jak osiągnąć ten cel za pomocą Workbox, znajdziesz w artykule Szybsze aplikacje wielostronicowe za pomocą strumieni.

Podsumowanie

Sposób, w jaki witryna odbiera i renderuje kod HTML, ma wpływ na jej wydajność. Jeśli polegasz na tym, że serwer wysyła cały kod HTML (lub jego większość) niezbędny do działania witryny, nie oznacza to, że wiele korzyści otrzymujesz bezpłatnie: przyrostowe analizowanie i renderowanie oraz automatyczne przekazywanie do wątku głównego, by uniknąć długich zadań.

Renderowanie HTML po stronie klienta wiąże się z szeregiem potencjalnych problemów z wydajnością, których w wielu przypadkach można uniknąć. Jednak ze względu na indywidualne wymagania poszczególnych witryn nie jest to w 100% przypadków możliwe do uniknięcia. Aby ograniczyć potencjalne długie zadania, które mogą wynikać z nadmiernego renderowania witryny klienta, upewnij się, że jak najwięcej kodu HTML swojej witryny wysyłasz z serwera, używaj jak najmniejszych rozmiarów DOM dla kodu HTML, który musi być renderowany przez klienta, i rozważ alternatywne architektury przyspieszające dostarczanie kodu HTML do klienta, a jednocześnie wykorzystaj możliwości przyrostowego analizowania i renderowania kodu HTML ładowanego z serwera.

Ograniczając do minimum możliwości renderowania po stronie klienta, poprawisz nie tylko wartość INP witryny, ale także inne dane, takie jak LCP, TBT, a w niektórych przypadkach nawet TTFB.

Baner powitalny z albumu Unsplash, autor: Maik Jonietz.