Strona główna / Artykuły / HTMX kontra domyślny SPA: Kiedy hypermedia przewyższa frameworki JavaScript

HTMX kontra domyślny SPA: Kiedy hypermedia przewyższa frameworki JavaScript

Jak atrybuty htmx zastępują renderowanie po stronie klienta, co naprawdę oznaczają rozmiary plików bundle oraz zrównoważony przewodnik pokazujący, w których przypadkach aplikacje hypermedia mają przewagę, a w których nadal React.

2260 słów

Wiele zespołów wybiera React przy każdym nowym projekcie, w tym panele administracyjne oraz narzędzia typu CRUD, które składają się głównie z form i tabel. htmx twierdzi, że duża część tych aplikacji w ogóle nie potrzebuje frameworka po stronie klienta: serwer może wysyłać HTML, a kilka atrybutów pozwala każdemu elementowi na pobieranie i wymianę jego fragmentów. Ten przewodnik wyjaśnia, jak działa htmx, porównuje jego wymagania z wymaganiami Reacta oraz przedstawia zarówno rzeczywiste zalety, jak i ograniczenia, których często nie uwzględniają zwolennicy Reacta, abyś mógł świadomie wybrać odpowiednią architekturę, a nie z przyzwyczajenia.

Zakłada się, że budowałeś aplikacje internetowe i spędziłeś większość czasu na pracy z Reactem lub podobnym frameworkiem.

Jak SPA stało się standardem dla wszystkiego

Mniej więcej w 2013 roku branża zmieniła swój podstawowy model. Zamiast serwerów zwracających strony HTML, zaczęły one zwracać JSON, a JavaScript w przeglądarce przekształcał ten JSON na interfejs.

Dla niektórych produktów aplikacje jednostronicowe stanowiły prawdziwy krok naprzód. Gmail, Figma i Google Maps przechowują dużo danych w przeglądarce, a ich interakcje muszą wydawać się natychmiastowe.

Problem polega na tym, że ten model rozprzestrzenił się na wszystko. Wewnętrzna karta kontrolna, która wyświetla wiersze i umożliwia ich edycję, ostatecznie miała tę samą architekturę co narzędzie do projektowania. Strona marketingowa z jednym formularzem kontaktowym uzyskała narzędzie do pakowania, router, bibliotekę do zarządzania stanem oraz proces hydratacji.

Taki standard ma konkretne konsekwencje finansowe:

  • Dwa zbiory kodu, często w dwóch językach
  • Model danych zdefiniowany po obu stronach
  • Łańcuch narzędzi do budowania, który trzeba utrzymywać i aktualizować
  • Trwała konieczność synchronizacji stanu klienta ze stanem serwera
  • Głównym twierdzeniem htmx jest to, że wiele aplikacji ponosi te koszty bez uzyskania niczego w zamian.

    Czym jest htmx

    htmx to mała biblioteka JavaScript, która rozszerza HTML o atrybuty. Dzięki nim każdy element może wysłać żądanie HTTP i umieścić odpowiedź w dowolnym miejscu na stronie. To właśnie stanowi istotę całej biblioteki.

    Pole wyszukiwania w czasie rzeczywistym ilustruje tę koncepcję. Pole wejścia określa, do którego URL należy się udostępnić, który zdarzenie uruchamia to żądanie oraz gdzie powinny trafić wyniki:

    <input type="text"
           name="q"
           hx-get="/search"
           hx-trigger="keyup changed delay:300ms"
           hx-target="#results">
    
    <div id="results"></div>
    

    Czytaj atrybuty jako zdanie: gdy klawisz zostanie zwolniony, a wartość rzeczywiście się zmieniła, poczekaj 300 ms, wyślij żądanie GET do /search i umieść to, co wróci, wewnątrz #results. Modyfikator delay:300ms zapobiega nadmiernym żądaniom, dzięki czemu szybkie pisanie nie powoduje wysyłania żądania za każdym razem, gdy naciśnięty zostanie klawisz.

    Serwer nie zwraca JSON. Zwraca gotowy fragment HTML:

    <ul>
      <li>First result</li>
      <li>Second result</li>
    </ul>
    

    htmx wstawia ten fragment do elementu docelowego. Nie ma żadnego parsowania JSON, żadnych szablonów po stronie klienta ani stanu klienta, który mógłby odbiegać od tego na serwerze.

    Atrybuty, których będziesz używać najczęściej

    • hx-get, hx-post, hx-put, hx-delete – służą do wyboru metody HTTP i adresu URL.
  • hx-trigger określa zdarzenie, które uruchamia żądanie, takie jak click, keyup, load, every 2s lub revealed (gdy element pojawia się na ekranie podczas przewijania).
  • hx-target wybiera element, który otrzymuje odpowiedź.
  • hx-swap kontroluje sposób wstawiania odpowiedzi: innerHTML, outerHTML, beforeend lub delete.
  • hx-indicator wskazuje na element pokazywany podczas wysyłania żądania.
  • hx-confirm prosi użytkownika o potwierdzenie przed wysłaniem.
  • To dobrze się łączy. Następny fragment kodu usuwa wiersz tabeli: przycisk wysyła żądanie DELETE, celuje w najbliższy otaczający go element tr, zastępuje cały ten wiersz odpowiedzią (zwykle pustą) i najpierw prosi o potwierdzenie. Modyfikator swap:1s opóźnia tę zamianę o sekundę, co daje czas na przejście CSS do zanikania wiersza i sprawia, że działanie wydaje się responsywne:

    <tr>
      <td>Widget</td>
      <td>
        <button hx-delete="/items/42"
                hx-target="closest tr"
                hx-swap="outerHTML swap:1s"
                hx-confirm="Delete this item?">
          Delete
        </button>
      </td>
    </tr>
    

    Zwróć uwagę na to, czego brakuje: nie ma oddzielnego pliku JavaScripta, żadnego kroku budowania ani stanu po stronie klienta. Serwer nadal odpowiada za usunięcie elementu i decydowanie o tym, czym ma się stać wiersz.

    Dwie architektury obok siebie

    Różnica jest bardziej widoczna w formie przepływu niż w porównaniu narzędzi.

    • Model SPA: serwer wysyła JSON, kod klienta go renderuje i przechowuje stan, użytkownik podejmuje działanie, klient aktualizuje swój stan, ponownie go renderuje i wysyła z powrotem JSON.
    • Model hipermediów: serwer wysyła HTML, przeglądarka go wyświetla, użytkownik podejmuje działanie, serwer odpowiada nowym HTML, a przeglądarka go zastępuje.

    W modelu hipermediów istnieje dokładnie jedno źródło prawdy: serwer. To eliminuje całą klasę błędów, w których klient wierzy w coś innego niż to, co mówi baza danych, takie jak przestarzałe cache’i lub optymistyczne aktualizacje, które nigdy nie zostały skonsolidowane.

    Carson Gross, twórca htmx, przedstawia to jako powrót do tego, czym REST miał być pierwotnie. REST Roya Fieldinga obejmuje ograniczenie HATEOAS (Hypermedia As The Engine Of Application State): serwer wysyła reprezentacje zawierające informacje o dostępnych dalszych działaniach. Typowa API JSON tego nie robi; klient musi z góry wiedzieć, jakie punkty końcowe istnieją. HTML robi to w sposób naturalny, ponieważ link stanowi przejście między stanami, a formularz to działanie. To, czy ten podejście wydaje się ci przenikliwe, czy raczej akademickie, jest dobrym wskaźnikiem twojego odczucia wobec htmx jako całości.

    Mierzenie rozmiaru pliku i to, czego nie mówi

    Cytowane rozmiary się różnią, więc pomocne jest ich pomiar. W momencie pisania minifikowana wersja htmx ważyła tak wiele:

    htmx.min.js:  51,238 bytes raw
                  16,576 bytes gzipped   (16.2 KB)
    

    Wersje produkcyjne React 19.2.8, pakiet React wraz z klientem DOM, miały następującą wagę:

    react.production.js:            4,446 bytes gzipped
    react-dom-client.production.js: 94,757 bytes gzipped
    combined:                       98,420 bytes gzipped   (96 KB)
    

    To oznacza różnicę około sześciokrotną i dotyczy ona jedynie środowiska działania React. Rzeczywista aplikacja React zawiera router, bibliotekę do zarządzania stanem, warstwę pobierania danych oraz własne komponenty, więc pliki końcowe często osiągają kilkaset kilobajtów. Natomiast wartość htmx odnosi się do całkowitych zależności po stronie klienta. Dokładne liczby zmieniają się przy każdym wydaniu, więc należy ponownie je sprawdzić w odniesieniu do wersji, które faktycznie zostaną wydane.

    Należy jednak uważać przy wyciąganiu wniosków. Rozmiar pliku wpływa na pierwsze załadowanie, a nie na szybkość interakcji później. Przy dobrej łączności różnica w czasie ładowania jest niewielka, a przy ponownych odwiedzinach oba pliki pochodzą z pamięci cache. Bardziej przekonującym argumentem dotyczącym wydajności jest fakt, że htmx nie ma fazy hydratacji – okresu, w którym strona React wygenerowana na serwerze wygląda na gotową, ale ignoruje kliknięcia, dopóki nie zostaną dołączone jej obsługi zdarzeń.

    Gdzie htmx naprawdę pomaga

    • Jeden język i jedna baza kodu. Zespół pracujący z Go, Pythonem, Ruby lub C# może stworzyć całe aplikację. Walidacja znajduje się w jednym miejscu, a model danych jest definiowany tylko raz.
    • Brak pipeline budowania. Wystarczy pojedyncza tag script. Nie ma konfiguracji bundlera, nie ma plików node_modules frontendu ani konieczności aktualizacji zależności frontendowych.
    • Lokalność zachowania. To, co robi dany element, jest zapisane bezpośrednio w elemencie. Zamiast śledzić kliknięcie przez kilka plików i bazę danych, czyta się sam markup. To jeden z najsilniejszych argumentów na rzecz tego podejścia, a dotyczy on utrzymywalności, a nie szybkości.
    • Stan w jednym miejscu. Nie ma karty klienta do unieważnienia, żadnych przestarzałych danych ani konieczności cofania optymistycznych aktualizacji.
  • Mniej kodu frontendowego do operacji CRUD. Zespoły przenoszące aplikacje intensywnie wykorzystujące CRUD na htmx często donoszą o zmniejszeniu objętości kodu frontendowego o 40 do 60 procent. Ta liczba jest powszechnie rozpowszechniana, choć nie ma jasnego źródła pierwotnego, więc należy ją traktować jako anegdotę; jednak kierunek jest spójny we wszystkich raportach.
  • SEO i dostępność jako standard. Wynikiem jest HTML generowany na serwerze, więc boty mogą przeglądać treść, a technologie wspomagające uzyskują prawdziwe znaczniki semantyczne, pod warunkiem że używasz poprawnych znaczników.
  • Gdzie htmx ustępuje

    To są kwestie, które często pozostają nieporuszone.

    Bogata, ciągła interakcja

    Edytory typu przeciągnij i upuść, edytory na podkładce, arkusze kalkulacyjne oraz interaktywne wykresy z funkcjami przesuwania i powiększania wymagają ciągłej interakcji, a nie pojedynczych zapytań. W htmx każda interakcja oznacza dwukierunkową komunikację z serwerem. W przypadku edytora tekstu nie jest to żaden kompromis – właśnie dlatego htmx nie nadaje się do takich zastosowań.

    Opóźnienia w słabych sieciach

    Zwolennicy tego podejścia wskazują, że hosting na brzegu znacznie skraca czas dwukierunkowej komunikacji, co jest prawdą. Mimo to użytkownik połączony słabym łączeniem mobilnym na obszarach wiejskich może musieć czekać kilkaset milisekund na operację, którą React załatwiłby lokalnie w zaledwie kilka milisekund. Aplikacje htmx działają doskonale w dobrych sieciach, ale znacznie gorzej w słabych, co jest odwrotnością tego, jak zwykle przedstawia się ten argument.

    Brak zastosowań w telefonach mobilnych lub w trybie offline

    htmx jest dostępny wyłącznie w środowisku internetowym. React Native umożliwia zespołowi dzielenie się koncepcjami oraz znaczną ilością kodu z aplikacjami natywnymi, więc jeśli w planach jest rozwój aplikacji mobilnych natywnych, to może to przeważyć nad wszystkimi innymi czynnikami. Użycie offline również nie jest możliwe, ponieważ każda interakcja wymaga połączenia z serwerem.

    Złożony stan po stronie klienta

    Multystopniowe interfejsy z polemi wzajemnie powiązanymi, formularze z natychmiastową weryfikacją pól lub ekrany, na których kilka komponentów musi reagować na jedną zmianę, sprawiają trudności. Zespoły zazwyczaj dodają Alpine.js lub hyperscript, a wtedy faktycznie budują własną ramę z poszczególnych elementów, zamiast korzystać z gotowej.

    Mały ekosystem i mniejszy zasób talentów

    React posiada dojrzałe komponenty dla niemal każdego elementu interfejsu. Z htmx możesz albo stworzyć własny wybieracz dat, albo włączyć gotowy JavaScriptowy i samodzielnie zarządzać jego funkcjonowaniem. Ważna jest również znajomość technologii: według badań przeprowadzonych w momencie pisania tego tekstu, React jest używany przez ponad 40 procent programistów, podczas gdy htmx przez około 7 procent, a stosunek pobrań z npm wynosi około 560 do 1. To wpływa na to, jak szybko nowi pracownicy stają się produktywni oraz na to, jak łatwo można znaleźć rozwiązania w trudnych sytuacjach.

    Uważne analizowanie danych dotyczących adopcji

    Rzeczywisty obraz jest bardziej złożony, niż sugerują obie strony.

    • Użytkowanie Reacta nie spada. Pozostaje on dominujący w ujęciu bezwzględnym z bardzo dużą przewagą.
  • Zadowolenie z Reacta spada. Dane z ankiet pokazują, że jego użycie pozostaje stabilne, podczas gdy rośnie liczba odpowiedzi typu „nie użyłbym ponownie”. Ludzie nadal korzystają z Reacta, ale robią to z mniejszą przyjemnością, co jest ważnym sygnałem, ale nie oznacza porzucenia tego narzędzia.
  • Rozwój htmx jest względny. Przekroczył liczbę 40 000 gwiazdek na GitHubie, dodał około 16 800 w 2024 roku i zajął pierwsze miejsce w kategorii frontend w rankingu JavaScript Rising Stars. Jest to szybki rozwój przy niewielkiej bazie początkowej.
  • Podsumowując: React nie jest zastępowany, ale jego pozycja jako bezkrytycznie przyjętego standardu słabnie. Należy pamiętać, że wiele treści porównujących htmx z Reactem jest tworzonych pod kątem ruchu wyszukiwarki, a takie dane jak procent redukcji kodu czy różne wskaźniki szybkości są powtarzane bez podania źródła. Traktuj je jako orientacyjne i sprawdzaj aktualne źródła przed ich cytowaniem.

    Wybór pomiędzy nimi

    Wybierz htmx, gdy:

    • Aplikacja polega w głównej mierze na formularzach i listach: panelach administracyjnych, narzędziach wewnętrznych, aplikacjach CRUD, stronach z treścią oraz panelach kontrolnych prezentujących dane, a nie manipulujących nimi.
    • Silna strona twojego zespołu leży w warstwie backendowej.
    • Chcesz mieć jedną bazę kodu, którą za kilka lat będzie mógł utrzymywać każdy członek zespołu.

    Wybierz React, gdy:

    • Brauzer faktycznie przechowuje znaczną ilość stanu, jak w edytorach, narzędziach do projektowania, aplikacjach do współpracy w czasie rzeczywistym lub przy intensywnej manipulacji danymi.
    • Potrzebujesz aplikacji mobilnych typu native lub obsługi offline.
    • Zależysz od dojrzałego ekosystemu komponentów.

    Istnieje również trzecia opcja, która jest często pomijana: użycie obu narzędzi. htmx może obsługiwać ekrany CRUD, podczas gdy React zapewnia funkcjonalność dla tych dwóch lub trzech widoków, które tego naprawdę potrzebują. Nic nie zmusza do stosowania jednej architektury w całym produkcie, a łączenie htmx z elementami React jest łatwiejsze niż łączenie dwóch frameworki typu SPA. Jeśli już korzystasz z htmx, przewodnik aktualizacji na htmx 4 opisuje zmiany w następnej głównej wersji.

    Komponenty serwerowe React czerpią część idei hypermedia, przenosząc proces renderowania na serwer. Nadal jednak wymagają pełnego środowiska działania React oraz serwera obsługującego Node, więc nie stanowią lekkiej alternatywy; są to wersje React, które oferują niektóre z tych samych korzyści, zachowując przy tym swoją wagę.

    Główne wnioski

    • htmx umożliwia każdemu elementowi wysyłanie żądania i zastępowanie go HTML generowanym na serwerze, co utrzymuje serwer jako jedyny źródło prawdy i eliminuje konieczność synchronizacji stanu klienta.
    • Jego czas działania wynosi mniej więcej jedną szóstą czasu działania Reacta przed dodaniem jakiegokolwiek kodu aplikacji React, ale praktyczną korzyścią jest unikanie procesu hydratacji, a nie skrócenie czasu ładowania.
    • Sprawdza się w aplikacjach typu CRUD, narzędziach wewnętrznych i stronach z treścią, natomiast ma trudności przy ciągłej interakcji, słabych łączach, użyciu na urządzeniach mobilnych, pracy offline oraz złożonym stanie klienta.
    • Mieszanie obu podejść to prawidłowa architektura, a nie kompromis.

    Bardziej istotne pytanie w tym sporze brzmi, jak architektura zaprojektowana dla Gmail stała się standardem w formularzu z sześcioma polami. Nikt tak naprawdę nie mylił się; narzędzie stało się standardem, a standardy przestają być analizowane. Cokolwiek wybierzesz, włączając React, niech to będzie decyzja podjęta po namyśle, a nie taka, którą odziedziczyłeś.

    Literatura pokrewna

  • Timerzy zliczania wstecznego bez driftu w React: od setTimeout do czystego CSS — Porównanie funkcji setTimeout, requestAnimationFrame oraz techniki opartej wyłącznie na CSS bez użycia JavaScript do tworzenia timerów w React, w tym triku z pojedynczym opóźnieniem służącego do synchronizacji cyfr.
  • Budżety wydajności dla JavaScript: nakładanie ograniczeń na pakiety w środowisku CI — Dowiedz się, dlaczego JavaScript kosztuje znacznie więcej niż czas jego pobierania, jak określić realistyczny budżet wydajności oraz jak sprawić, by webpack odrzucał projekty przekraczające te limity.
  • Jedna prośba do dodania elementu, dwie architektury: Co htmx usuwa ze stosu CRUD — Prześledź jedną prośbę o dodanie elementu w aplikacji typu SPA z React oraz na stronie z htmx, aby zobaczyć, które warstwy znikają, co przejmuje serwer i gdzie htmx przestaje być przydatny.