- HTMX 사용 시 코드량을 약 70% 줄일 수 있었으나, UI 간 동기화 이슈와 더불어 프론트엔드 상태 관리의 복잡성 증가를 경험
- Datastar 도입 후, 실시간 멀티유저 애플리케이션 개발 시 WebSockets 없이 간결한 코드 구성 및 유지보수가 용이해짐
- HTMX가 HTML 속성을 중심으로 동작 로직을 분산시키는 반면, Datastar는 서버 주도적 업데이트 모델을 통해 로직의 일관성과 유지보수성을 높임
- Datastar API는 속성이 적고 코드의 가독성 및 생산성이 증가함을 느낌
- Datastar는 Server-Sent Events(SSE), Web Components, CSS View Transitions 등 웹 네이티브 기술을 적극 활용하여 실시간 협업과 재사용 가능한 컴포넌트 구조를 가능하게 함
소개 및 동기
- 2022년에 David Guillot가 DjangoCon Europe에서 React 기반 SaaS를 HTMX로 전환하면서 코드량을 약 70% 줄이고 기능 개선을 이룬 사례를 공유했었음
- 그 후에 다수의 팀들이 단일 페이지 앱(SPA)에서 멀티페이지 하이퍼미디어 앱으로 전환 시 코드 절감 및 개발∙사용자 경험 모두 향상됨을 경험함
- 필자 역시 HTMX에서 Datastar로 프로젝트를 전환하면서 코드가 더 짧아지고 WebSocket이나 복잡한 상태 관리 없이 실시간 멀티유저 앱 개발이 가능함을 확인
전환의 계기가 된 문제점
-
FlaskCon 2025 발표를 준비하던 중 HTMX와 AlpineJS를 조합하여 UI를 동기화하려 했으나, UI 동기화 문제에 직면
- 두 라이브러리는 서로 다른 개발자가 만든 별도 도구로 상호 통신이 불가능하여, 개발자가 직접 통합 작업을 담당해야 함
- 다양한 시점에 컴포넌트를 초기화하고 이벤트를 조율하는 과정에서 예상보다 많은 코드 작성과 디버깅 시간 소요
- Datastar가 두 라이브러리의 기능을 통합하면서도 11KB 미만의 크기로 제공한다는 점에 주목하여 시도해 봄
- 모바일 기기 사용자의 페이지 로드 성능 개선에 유리
더 나은 Datastar의 API 설계
- Datastar의 API가 HTMX보다 훨씬 가벼운 느낌을 주며, 원하는 결과를 얻기 위해 추가해야 하는 속성(attribute) 수가 적음
- HTMX는 대부분의 상호작용에서 여러 속성 필요
- URL 정의, 타겟 요소 지정, 응답 처리 방식 등을 각각 별도 속성으로 설정
- 일반적으로 2~3개의 속성을 매번 사용하며, 때로는 상속 체인을 따라 올라가며 속성 동작 방식을 확인해야 함
<a hx-target="#rebuild-bundle-status-button" hx-select="#rebuild-bundle-status-button" hx-swap="outerHTML" hx-trigger="click" hx-get="/rebuild/status-button"></a> - Datastar는 일반적으로 단 하나의 속성으로 동일한 기능 구현
<a data-on-click="@get('/rebuild/status-button')"></a>- 몇 개월 후 코드를 다시 봐도 동작 방식을 쉽게 이해 가능
동작 원리의 차이
- HTMX는 프론트엔드 라이브러리로 HTML 스펙을 확장하는 것을 목표로 하는 반면, Datastar는 서버 주도 라이브러리로 고성능 웹 네이티브 실시간 업데이트 애플리케이션 구축을 목표로 함
- HTMX는 요청을 트리거하는 요소에 속성을 추가하여 동작을 정의하며, 페이지의 멀리 떨어진 요소를 업데이트하더라도 로직이 여러 레이어에 분산됨
- Datastar는 서버가 무엇을 변경할지 결정하여 모든 업데이트 로직을 한 곳에 집중
-
HTMX 예제
<div> <div id="alert"></div> <button hx-get="/info" hx-select="#info-details" hx-swap="outerHTML" hx-select-oob="#alert"> Get Info! </button> </div>- 버튼을 누르면
/info로 GET 요청을 보내고, 응답의 'info-details' ID 요소로 버튼을 교체하며, 응답의 'alert' ID 요소로 페이지의 동일 ID 요소를 교체 - 버튼 요소가 알아야 할 정보가 너무 많으며, 서버에서 반환할 정보를 미리 알아야 하므로 HTMX의 "행동의 지역성(locality of behavior)" 원칙이 약화됨
- 버튼을 누르면
-
Datastar의 개선된 접근
<div> <div id="alert"></div> <button id="info-details" data-on-click="@get('/info')"> Get Info! </button> </div>- 서버가 동일한 ID를 가진 두 개의 루트 요소를 포함한 HTML 문자열 반환
<p id="info-details">These are the details you are looking for…</p> <div id="alert">Alert! This is a test.</div> - 간단하고 성능이 우수한 옵션
- 서버가 동일한 ID를 가진 두 개의 루트 요소를 포함한 HTML 문자열 반환
컴포넌트 레벨에서 사고하기
- 더 나은 접근 방식은 HTML을 컴포넌트로 취급하는 것
- 해당 컴포넌트의 본질 파악
- 사용자가 특정 항목에 대한 추가 정보를 얻는 방법
- 사용자가 버튼을 클릭하면 정보가 나타나거나 정보가 없어 오류가 렌더링되며, 어느 쪽이든 컴포넌트는 정적 상태가 됨
-
상태별 컴포넌트 분리
- 플레이스홀더 상태:
<!-- info-component-placeholder.html --> <div id="info-component"> <button data-on-click="@get('/product/{{product.id}}/info')"> Get Info! </button> </div> - 정보 표시 상태:
<!-- info-component-get.html --> <div id="info-component"> {% if alert %}<div id="alert">{{ alert }}</div>{% endif %} <p>{{product.additional_information}}</p> </div> - 서버가 HTML을 렌더링하면 Datastar가 페이지를 자동으로 업데이트
- 컴포넌트 레벨 사고는 잘못된 상태에 진입하거나 사용자 상태를 잃어버리는 것을 방지
- 플레이스홀더 상태:
여러 컴포넌트 동시 업데이트
- David Guillot의 발표에서 인상적이었던 점은 앱이 선호 항목 수를 업데이트할 때, 변경된 컴포넌트와 매우 멀리 떨어진 카운트 요소도 함께 업데이트되었다는 것
- HTMX는 JavaScript 이벤트를 트리거하고, 이것이 다시 원격 컴포넌트에 GET 요청을 발행하도록 트리거
- Datastar는 동기 함수 내에서도 여러 컴포넌트를 동시에 업데이트 가능
-
쇼핑 카트 예
- 장바구니 추가 컴포넌트:
<form id="purchase-item" data-on-submit="@post('/add-item', {contentType: 'form'})">" > <input type=hidden name="cart-id" value="{{cart.id}}"> <input type=hidden name="item-id" value="{{item.id}}"> <fieldset> <button data-on-click="$quantity -= 1">-</button> <label>Quantity <input name=quantity type=number data-bind-quantity value=1> </label> <button data-on-click="$quantity += 1">+</button> </fieldset> <button type=submit>Add to cart</button> {% if msg %} <p class=message>{{msg}}</p> {% endif %} </form> - 장바구니 카운트 표시 컴포넌트:
<div id="cart-count"> <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"> <use href="#shoppingCart"> </svg> {{count}} </div> - Django에서 두 컴포넌트를 동일한 요청으로 업데이트:
from datastar_py.consts import ElementPatchMode from datastar_py.django import ( DatastarResponse, ServerSentEventGenerator as SSE, ) def add_item(request): # 중요한 상태 업데이트 생략 return DatastarResponse([ SSE.patch_elements( render_to_string('purchase-item.html', context=dict(cart=cart, item=item, msg='Item added!')) ), SSE.patch_elements( render_to_string('cart-count.html', context=dict(count=item_count)) ), ])
- 장바구니 추가 컴포넌트:
웹 네이티브 철학
- Datastar Discord 커뮤니티를 통해 Datastar가 단순한 헬퍼 스크립트가 아니라 웹의 기본 프리미티브를 활용한 앱 구축 철학임을 이해
- HTMX가 HTML 스펙을 발전시키려는 반면, Datastar는 웹 네이티브 기능의 채택을 촉진하는 데 더 관심
- CSS view transitions
- Server-Sent Events
- 웹 컴포넌트 등
- 복잡한 AlpineJS 컴포넌트를 리팩토링하여 간단한 웹 컴포넌트를 추출하고 여러 곳에서 재사용하는 큰 성과 달성
- React 같은 도구 없이도 커스텀 HTML 요소 생성으로 높은 행동 지역성과 재사용성을 달성할 수 있는 훌륭한 패턴
다중 사용자 앱을 위한 실시간 업데이트
- 협업을 일급 기능으로 갖춘 앱은 다른 앱과 차별화되며, Datastar는 이 과제를 해결
- 대부분의 HTMX 개발자는 폴링 방식으로 서버에서 정보를 가져오거나 커스텀 WebSocket 코드를 작성하여 복잡도를 증가시킴
- Datastar는 Server-Sent Events(SSE) 라는 간단한 웹 기술을 사용하여 서버가 연결된 클라이언트에 업데이트를 "푸시"
- 사용자가 댓글을 추가하거나 상태가 변경되면 서버가 즉시 브라우저를 업데이트하며, 추가 코드가 최소화됨
- 커스텀 JavaScript 없이도 실시간 대시보드, 관리 패널, 협업 도구 구축 가능
- 클라이언트 연결이 중단되면 브라우저가 자동으로 재연결 시도하며, 추가 코드 불필요
- 서버에 "마지막으로 받은 이벤트"를 알릴 수도 있음
과도한 복잡성 피하기
- Datastar Discord 커뮤니티는 웹 앱 제작에 대한 Datastar의 비전을 이해하는 데 도움
- 푸시 기반 UI 업데이트
- 복잡성 감소
- 웹 컴포넌트 같은 도구를 활용한 로컬 복잡한 상황 처리
- 커뮤니티는 신규 사용자가 과도하게 복잡하게 접근하고 있음을 깨닫도록 도움
주요 팁
-
전체 컴포넌트를 다시 렌더링하여 전송하는 것을 두려워하지 말 것
- 더 쉬우며 성능에 큰 영향을 주지 않음
- 더 나은 압축률을 얻을 수 있고, 브라우저가 HTML 문자열을 파싱하는 속도가 매우 빠름
-
서버가 진실의 상태이며 브라우저보다 강력함
- 대부분의 상태를 서버가 처리하도록 하고, 생각보다 반응형 시그널이 필요하지 않을 수 있음
-
웹 컴포넌트는 높은 행동 지역성을 가진 커스텀 요소에 로직을 캡슐화하는 데 탁월
- Datastar 웹사이트 헤더의 별 필드 애니메이션이 좋은 예시
-
<ds-starfield>요소가 별 필드 애니메이션의 모든 코드를 캡슐화하고 내부 상태를 변경할 세 가지 속성을 노출 - Datastar가 범위 입력이 변경되거나 마우스가 요소 위를 움직일 때 속성을 구동
한계를 뛰어넘는 가능성
- Datastar가 가능하게 하는 잠재력이 가장 흥미로움
- 커뮤니티는 다른 도구를 사용하는 개발자가 경험하는 한계를 훨씬 뛰어넘는 프로젝트를 정기적으로 생성
주목할 만한 사례
- 예시 페이지의 데이터베이스 모니터링 데모
- Hypermedia를 활용하여 JavaScript 컨퍼런스에서 발표된 데모의 속도와 메모리 사용량을 크게 개선
- Anders Murphy의 10억 개 체크박스
- 100만 개 체크박스 실험이 서버 용량을 초과하자, Datastar를 사용하여 저렴한 서버에서 10억 개 구현
- 미국의 모든 레이더 스테이션 데이터를 표시하는 웹 앱
- 레이더의 신호가 변경되면 UI의 해당 점이 100밀리초 이내에 변경
- 초당 80만 개 이상의 포인트가 업데이트되며, 사용자는 최대 1시간 전으로 스크럽 가능(700밀리초 미만의 지연)
- Hypermedia 앱으로서 이것이 가능하다는 것이 Datastar가 가능하게 하는 것
현재 사용 경험
- 여전히 Datastar의 탐색 단계에 있으며, 표준 HTMX 기능의 UI 업데이트 AJAX 처리를 빠르고 쉽게 구현
- Datastar를 사용하여 더 많은 것을 달성하기 위한 다양한 패턴을 학습하고 실험 중
- 수십 년 동안 실시간 업데이트로 더 나은 사용자 경험을 제공하는 방법에 관심이 있었으며, Datastar가 동기 코드에서도 푸시 기반 업데이트를 가능하게 하는 것이 좋음
- HTMX를 사용하기 시작했을 때 큰 기쁨을 느꼈지만, Datastar로 전환한 이후 잃은 것이 없다고 느끼며, 오히려 훨씬 더 많은 것을 얻었다고 느낌
- HTMX를 사용하면서 기쁨을 느꼈다면, Datastar에서도 동일한 도약을 다시 느낄 것이며, 이는 웹이 원래 해야 할 일을 발견하는 것과 같음