Gumroad에서는 최근 Helper라는 새로운 프로젝트에 착수했습니다. CEO로서 저는 이 프로젝트에 htmx를 사용하는 것에 대해 꽤 긍정적이었지만, 팀의 몇몇 멤버들은 이에 대해 그다지 열정적이지 않았습니다.
저의 낙관적인 생각은 이전에 리액트를 사용해 본 경험에서 비롯되었습니다. 리액트는 우리 요구사항에 비해 과하다고 느꼈습니다. 그렇기 때문에 htmx가 프론트엔드를 매우 가볍게 유지하는 데 좋은 솔루션이 될 수 있다고 생각했습니다.
사실 저는 슬랙에서 팀원들과 이러한 생각을 공유했습니다.
"https://htmx.org/는 간단한 상호작용을 추가하는 방법이 될 수 있습니다."
처음에는 좋은 선택처럼 보였습니다! 하지만, Gumroad의 한 엔지니어는 이렇게 말했죠.
"HTMX는 (공식적으로는) JS 환경이 지나치게 복잡해졌음을 조롱하는 밈으로, 테일윈드가 인라인 CSS의 다른 구문인 것처럼 HTMX는 인라인 JS의 다른 구문일 뿐입니다."
그러나 툴킷에서 자리를 잡은 테일윈드와 달리 htmx는 우리의 목적에 맞게 확장되지 않았고, 적어도 우리의 사용 사례에서는 고객에게 최고의 사용자 경험을 제공하지 못했습니다.
그 이유는 다음과 같습니다.
직관성 및 개발자 경험: htmx에서도 제대로 작동할 수 있었겠지만, Next.js로 모든 것을 구현하는 것이 훨씬 더 직관적이고 재미있었습니다. Next.js에서는 개발 프로세스가 자연스럽게 느껴졌지만, htmx에서는 부자연스럽고 강제적으로 느껴지는 경우가 많았습니다. 예를 들어, 동적 유효성 검사 및 조건부 필드가 있는 복잡한 form을 만들 때 리액트에서는 간단한 클라이언트 측 작업을 처리하기 위해 복잡한 서버 로직을 작성해야 했습니다.
UX의 한계: htmx는 결국 앱을 Rails에서 CRUD를 다루는 방식으로 밀어붙였고, 그 결과 기본적으로 열악한 사용자 경험(적어도 지루하고 일반적인)을 만들었습니다. 우리는 이런 비생산적인 상황에 지속적으로 싸워야 했습니다. 예를 들어, 워크플로우 빌더에 드래그 앤 드론 인터페이스를 구현하는 것은 상당히 어려웠습니다. htmx에서는 많은 우회 방법이 필요했고, 리액트 라이브러리를 통해 얻을 수 있는 매끄러운 경험에 비해 어색하게 느껴졌습니다.
AI와 도구 지원: Next.js는 AI 도구들이 친숙하게 인식하는 반면, htmx에는 오픈 소스 학습 데이터의 부족으로 인해 그렇지 않았습니다. 이는 Rails가 직면한 문제와 유사합니다. 비록 결정적인 문제는 아니었지만, 개발 속도와 문제 해결의 용이성에 영향을 미쳤습니다. 문제가 발생했을 때 리액트/Next.js에 사용할 수 있는 풍부한 리소스 덕분에 문제 해결이 훨씬 빨라졌습니다.
확장성 문제: 프로젝트가 복잡해지면서 htmx가 우리의 요구 사항을 따라가지 못한다는 것을 느꼈습니다. 처음에 매력을 느꼈던 단순함은 더 정교한 상호 작용과 상태 관리를 구현하려고 시도하면서 제약을 가져오기 시작했습니다. 예를 들어, 실시간 공동 작업과 복잡한 데이터 시각화 같은 기능을 추가하면서 여러 컴포넌트 간의 상태를 관리하는 것이 htmx의 서버 중심 접근 방식에서는 점점 더 어려워졌습니다.
커뮤니티와 생태계: 리액트/Next.js 생태계는 방대하고 성숙하여 우리가 직면한 거의 모든 문제에 대한 솔루션을 제공합니다. htmx를 사용하면서 우리는 종종 새롭게 개발하거나 기능을 타협해야 했습니다. 이 문제는 특히 써드파티 서비스와 라이브러리를 통합해야 할 때 더욱 분명합니다. 종종 리액트에 바인딩 되는 기능은 있지만 htmx에 상응하는 기능은 없는 경우가 많았습니다.
[NextJS를 사용한 소스 - 이미지 클릭하여 보기]
결국 저희는 리액트/Next.js로 전환했고, 이는 저희가 찾던 복잡한 UX를 구축하는 데 매우 적합했습니다. 현재로서는 이 결정에 만족하고 있습니다.
이를 통해 더 빠르게 움직이고, 더 매력적인 사용자 경험을 만들고, 기존의 다양한 도구와 라이브러리를 활용할 수 있게 되었으니까요.
[Gumroad Helper 전과 후 - 이미지 클릭하여 보기]
이 경험을 통해 얻은 소중한 교훈은 가벼운 대안을 고려하는 것도 중요하지만, 프로젝트와 함께 성장하고 장기적인 비전을 지원할 수 있는 기술을 선택하는 것도 마찬가지로 중요하다는 것입니다. Helper의 경우 리액트와 Next.js의 선택이 옳았다는 것을 입증했습니다.
이전 후 핵심 고객을 위해 앱 사용자 경험을 대폭 업그레이드할 수 있었습니다.
드래그 앤드 드롭 기능: 워크플로우 빌더의 핵심 기능 중 하나는 드래그 앤드 드롭을 통해 단계를 재정렬할 수 있는 기능입니다. htmx로 드래그 앤드 드롭을 구현할 수 있지만, 사용할 수 있는 솔루션이 투박하고 상당한 커스텀 자바스크립트가 필요하다는 것을 알게 되었습니다. 이에 반해 리액트 생태계는 최소한의 설정으로 부드럽게 동작하고 접근성을 준수하는 드래그 앤드 드롭을 제공한 react-beautiful-dnd와 같은 라이브러리를 제공합니다.
복잡한 상태 관리: 각 워크플로우 단계에는 고유한 구성과 조건부 로직이 있습니다. 사용자가 이를 편집할 때 다른 단계에 미치는 영향을 UI에 실시간으로 반영해야 합니다. 이를 위해서는 수많은 서버요청이나 복잡한 클라이언트 측 상태 관리가 필요하며, 이는 htmx의 서버 중심 철학에 어긋납니다. 리액트의 상태 관리 솔루션(예: useState 또는 Redux와 같은 고급 옵션)은 이 작업을 훨씬 더 간단하게 만들어줍니다.
동적 폼(Form) 생성: 각 단계 유형에 대한 구성은 다르며 사용자 입력에 따라 변경될 수 있습니다. 이러한 동적 폼을 생성하고 상태를 처리하는 것은 리액트 컴포넌트 모델에서 더 직관적이었습니다. htmx에서는 이러한 폼을 생성하고 유효성 검사를 위해 더 복잡한 서버 로직을 작성해야 했습니다.
실시간 공동 작업: 이 스크린샷에는 보이지 않지만, 여러 사용자가 동시에 워크플로우를 편집할 수 있는 기능을 구현했습니다. 웹 소켓과 리액트로 구현하는 것은 비교적 간단했지만, htmx를 사용했다면 실시간 업데이트를 처리하기 위해 더 복잡한 서버 로직과 커스텀 자바스크립트가 필요했을 것입니다.
성능 최적화: 워크플로우가 점점 더 커지고 복잡해지면서 렌더링 최적화에 대한 세밀한 제어가 필요했습니다. 리액트의 가상 DOM과 useMemo 및 useCallback 같은 훅을 통해 htmx에서는 쉽게 사용할 수 없거나 직관적이지 않았던 방식으로 성능을 최적화할 수 있었습니다.
이런 문제가 htmx로 극복할 수 없는 것은 아니지만, 이와 같은 문제를 해결하다 보면 htmx의 강점에서 벗어나 자바스크립트가 많은 환경에서 더 자연스럽게 느껴지는 솔루션을 찾게 되는 경우가 많다는 것을 알게 되었습니다. 이런 깨달음은 리액트와 Next.js로 전환하기로 하는 데 큰 영향을 미쳤습니다.
우리는 많은 프로젝트, 특히 단순한 상호작용 모델을 사용하는 프로젝트나 기존 서버 렌더링 애플리케이션 위에 구축된 프로젝트에 htmx가 적합할 수 있음을 인정합니다. 우리가 htmx를 선택하지 않았다고 해서, 다른 개발자들이 htmx를 통해 얻은 긍정적인 경험이 잘못되었다는 것은 아닙니다. 핵심은 프로젝트의 특정 요구 사항을 이해하고 요구 사항에 가장 적합한 도구를 선택하는 것입니다.
우리의 경우 Helper 인터페이스의 복잡하고 상태 특성으로 인해 리액트와 Next.js가 더 적합했습니다. 그러나 우리는 htmx의 접근 방식을 계속 높이 평가하고 있으며 향후 프로젝트에서 htmx의 강점이 우리의 요구 사항과 더 잘 부합하는 경우 다시 한번 고려할 수 있습니다.
하지만 요구사항이 진화하고 새로운 기술이 등장함에 따라 기술 스택을 재평가할 기회는 언제나 열려 있습니다. 미래에 어떤 일이 일어날지 누가 알겠습니까?