Skip to content

✍️ 필사 모드: HTMX & Hyperscript — 反-SPA 진영의 2026, Carson Gross가 던진 하이퍼미디어 명제 심층 (2026)

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

프롤로그 — SPA 피로, 그리고 Carson의 한 문장

2026년 5월, 프런트엔드 개발자 한 사람의 평균적인 하루를 그려보자.

pnpm install이 끝나기를 기다리는 동안 커피를 한 잔 더 끓이고, Vite dev server를 띄우고, React Query 캐시 무효화 정책을 다시 짠다. 어제 잘 돌던 빌드는 오늘 Module not found: Can't resolve 'crypto'로 죽었고, 누군가 next.config.js에 webpack 폴리필을 추가했더니 이번에는 Turbopack이 화를 낸다. 폼 하나를 만드는 데 — useState, useEffect, useMutation, zod 스키마, react-hook-form 리졸버, optimistic update 로직, 에러 바운더리, 로딩 스피너 — 일곱 개의 추상이 동원된다. 그 폼이 하는 일은 결국 POST /todos이고, 응답을 받아 화면을 갱신하는 것이다.

이 익숙한 풍경에 한 사람이 칼을 꽂았다. 몽고메리 대학 컴퓨터 과학과 교수, Carson Gross. 그는 2020년 intercooler.js를 다시 쓴 라이브러리에 HTMX라는 이름을 붙였고, 한 문장으로 시대를 요약했다.

"HTML은 이미 하이퍼미디어다. 우리가 한 일은 그 잠재력을 11KB로 풀어준 것뿐이다."

HTMX는 새로운 프레임워크가 아니다. HTML에 네 개의 속성을 추가했을 뿐이다. hx-get, hx-post, hx-swap, hx-target. 폼은 더 이상 JavaScript 핸들러를 필요로 하지 않는다. 버튼이 직접 서버에 POST를 보내고, 서버는 HTML 조각을 돌려주며, 그 조각이 페이지의 일부를 교체한다. 그것뿐이다.

그러나 그 단순함은 단순한 문법 이상의 무언가다. Carson은 그 위에 책 한 권을 얹었다. 『Hypermedia Systems』 — Roy Fielding의 REST 논문을 다시 읽고, HATEOAS를 진지하게 받아들이며, "왜 우리는 JSON API를 만들고, 그 위에 클라이언트 사이드 라우터를 만들고, 그 위에 상태 관리를 만들고, 그 위에 캐싱을 만들고, 그 위에 최적화 컴파일러를 만들었는가"를 묻는 책이다. 답은 도발적이다. "필요하지 않았다."

이 글은 HTMX 진영 — 反-SPA 진영, 또는 "boring web" 르네상스 — 의 2026년을 정리한다. HTMX 2.x의 코어, _hyperscript라는 Carson의 두 번째 작품, Datastar라는 차세대 후보, Alpine.js라는 타협안, 『Hypermedia Systems』가 제시한 사고 프레임, 37signals와 같은 프로덕션 사례, 그리고 — 가장 중요하게 — HTMX가 틀린 곳까지.

이 글은 HTMX 옹호문이 아니다. 언제 HTMX가 옳고 언제 React가 옳은지 결정하는 데 필요한 모든 도구를 한 호흡으로 제공하는 것이 목표다.


1장 · 하이퍼미디어 명제 — Roy Fielding을 다시 읽는다

HTMX의 모든 주장은 한 권의 박사 논문에서 시작된다. 2000년, Roy Fielding의 "Architectural Styles and the Design of Network-based Software Architectures" — 우리가 줄여서 REST라고 부르는 그것이다.

Fielding은 REST에 여섯 개의 제약을 두었다. 클라이언트-서버, 무상태, 캐시 가능, 통일된 인터페이스, 계층화된 시스템, 그리고 — HATEOAS: Hypermedia As The Engine Of Application State.

마지막 제약이 핵심이다. HATEOAS는 "클라이언트는 다음 행동에 대한 정보를 서버가 응답으로 보내준 하이퍼미디어로부터만 얻어야 한다"고 말한다. 즉, 응답에 "이 다음에 할 수 있는 것은 이러이러하다"는 정보가 링크와 폼의 형태로 들어 있어야 한다는 것이다.

Carson의 진단은 이렇다.

  • 오늘날 우리가 REST라고 부르는 것의 99%는 HATEOAS를 제외한 5개의 제약만 따른다.
  • JSON API는 데이터만 주고 행동을 주지 않는다. 그래서 클라이언트가 "다음에 무엇을 할 수 있는가"를 코드로 적어 두어야 한다.
  • 그 결과 클라이언트는 도메인 모델의 복제본을 갖게 되고, 서버와 클라이언트가 두 번 동기화를 해야 하는 시스템이 된다.
  • 반면 HTML은 그 자체로 하이퍼미디어다. <form> 태그는 행동을 포함하고, <a> 태그는 상태 전이를 포함한다.

HTMX의 명제는 그래서 단순하다.

"브라우저는 이미 우수한 하이퍼미디어 클라이언트다. HTMX는 그 클라이언트의 어휘를 확장한다."

기본 HTML은 "링크 클릭" 또는 "폼 제출" 시에만 서버 요청이 일어난다. HTMX는 그것을 확장한다.

  • 어떤 이벤트도 트리거가 될 수 있다 (hx-trigger)
  • 어떤 HTTP 동사도 사용할 수 있다 (hx-get, hx-post, hx-put, hx-patch, hx-delete)
  • 응답을 페이지의 어느 부분에든 교체할 수 있다 (hx-target, hx-swap)

이것이 HTMX의 전부다. 한 줄로 요약하면 — "any element, any event, any HTTP verb, any swap target."


2장 · HTMX 2.x 코어 — 네 개의 속성으로 99%를 만든다

2024년 1월, HTMX 2.0이 GA로 나왔다. 2025년에 2.1, 2026년 초에 2.2가 나왔고, 현재 안정 버전은 htmx.org 기준 11.4 KB(gzipped). 의존성은 zero. IE11 지원은 1.x에 남기고 2.x는 evergreen 브라우저 전용으로 단순화되었다.

2.1 설치 — script 태그 한 줄

<script src="https://unpkg.com/htmx.org@2.0.4"></script>

이게 전부다. npm 패키지도 있지만, HTMX는 <script> 태그로 시작하는 것을 명시적으로 권장한다. 빌드 스텝 없음, 번들러 없음, 트리 셰이킹 걱정 없음.

2.2 첫 번째 예제 — 검색 폼

전통적인 SPA에서 "타이핑하면 결과가 라이브로 갱신되는 검색"을 만들려면 — useState, useDeferredValue, debounce 훅, 캐시 무효화, abort controller, 로딩 상태 머신 — 최소 50줄의 React 코드가 필요하다.

HTMX 버전:

<input type="search" name="q"
       hx-get="/search"
       hx-trigger="input changed delay:300ms, search"
       hx-target="#results"
       hx-indicator=".spinner" />

<div id="results"></div>
<div class="spinner htmx-indicator">Searching...</div>

여기서 일어나는 일을 한 줄씩 읽어보자.

  • hx-get="/search" — 트리거되면 GET /search?q=...를 보낸다. name="q"가 자동으로 쿼리 파라미터가 된다.
  • hx-trigger="input changed delay:300ms, search"input 이벤트가 발생하고 값이 변경되었으며 300ms 동안 다른 이벤트가 없을 때 트리거. 또는 사용자가 검색 키를 눌렀을 때.
  • hx-target="#results" — 응답으로 #results의 내용을 교체.
  • hx-indicator=".spinner" — 요청 중에 .spinnerhtmx-request 클래스를 추가. CSS로 표시/숨김 처리.

서버는? Express, FastAPI, Django, Rails, Phoenix, Spring — 무엇이든 HTML 조각만 돌려주면 된다.

@app.get("/search")
def search(q: str):
    results = db.search(q)
    return render_template("results_fragment.html", results=results)

이 패턴이 갖는 의미는 깊다. 로딩 상태, debounce, abort, race condition, 캐싱 — 이 모든 것을 HTMX가 처리한다. 우리는 비즈니스 로직만 적는다.

2.3 코어 속성 13개

HTMX의 표면적은 작다. 13개의 핵심 속성으로 거의 모든 것이 표현된다.

속성역할
hx-get / hx-post / hx-put / hx-patch / hx-deleteHTTP 요청 발사
hx-trigger어떤 이벤트로 트리거할지 (click, input, every 2s, revealed, ...)
hx-target응답을 어느 엘리먼트에 적용할지 (CSS 선택자)
hx-swap어떻게 적용할지 (innerHTML, outerHTML, beforebegin, afterend, ...)
hx-vals요청에 추가할 값 (JSON 또는 JS 함수)
hx-headers커스텀 헤더
hx-indicator요청 중 인디케이터
hx-confirm전송 전 confirm() 다이얼로그
hx-push-url응답을 받을 때 브라우저 URL 갱신
hx-boost일반 링크/폼을 AJAX로 자동 업그레이드

hx-boost만으로도 흥미롭다. <body hx-boost="true"> 한 줄을 추가하면, 페이지의 모든 <a><form>이 자동으로 AJAX 요청을 보내고, 응답에서 body 부분만 교체한다. Turbo·Inertia와 비슷한 효과를 한 속성으로.

2.4 hx-swap의 위력 — DOM 조작이 선언적이 된다

hx-swap은 응답 HTML을 DOM에 적용하는 방법을 결정한다.

  • innerHTML (기본): 타깃의 내부를 교체
  • outerHTML: 타깃 자체를 교체
  • beforebegin / afterbegin / beforeend / afterend: 인접 위치에 삽입
  • delete: 타깃 삭제 (응답 무시)
  • none: DOM 변경 없음 (사이드 이펙트만)

여기에 hx-swap="outerHTML swap:1s settle:1s scroll:#top" 같은 modifier를 붙이면 — swap 전 1초 대기(애니메이션), settle 단계 1초(transition class 적용), 완료 후 #top으로 스크롤 — 이 모든 것이 한 줄에 표현된다.

2.5 hx-trigger의 위력 — 이벤트 DSL

hx-trigger는 작은 DSL이다.

<!-- 5초마다 자동 갱신 -->
<div hx-get="/notifications" hx-trigger="every 5s"></div>

<!-- 뷰포트에 들어오면 한 번만 트리거 (lazy load) -->
<div hx-get="/heavy-section" hx-trigger="revealed"></div>

<!-- 다른 엘리먼트의 이벤트로 트리거 -->
<div hx-get="/related" hx-trigger="updated from:#article"></div>

<!-- 키보드 단축키 -->
<form hx-post="/save" hx-trigger="keyup[ctrlKey&&key=='s'] from:body"></form>

every Ns로 폴링이 한 줄이 된다. revealed로 무한 스크롤이 한 줄이 된다. from:#selector로 다른 엘리먼트의 이벤트를 listen한다. WebSocket 없이도 polling이 충분한 영역에서 — 알림, 주식 시세, 라이브 카운터 — HTMX는 즉시 답을 준다.


3장 · Hyperscript — Carson의 두 번째 도발

HTMX는 서버 왕복으로 처리할 수 있는 일을 다룬다. 그러나 "버튼을 클릭하면 옆에 있는 div의 클래스를 토글한다" 같은 순수 클라이언트 사이드 동작도 우리는 자주 필요하다. 이때 React를 끌어오기는 과한다. jQuery는 너무 옛날 같다.

Carson은 이 빈자리에 _hyperscript (밑줄로 시작, 줄임말 _hs)를 놓았다. 영어 문장처럼 읽히는 이벤트 핸들러 DSL.

<button _="on click toggle .active on #panel">Toggle</button>

문장으로 읽어보자. "on click, toggle the class .active on #panel." 영어 그대로다. 파서는 이걸 그대로 받아들인다. HyperTalk(HyperCard의 스크립트 언어)에서 영감을 받았다.

3.1 더 복잡한 예

<input _="on input
            if my value's length < 3
              hide #suggestions
            else
              show #suggestions
              put 'Searching...' into #suggestions
            end" />

이게 무엇이냐. 입력 길이가 3 미만이면 #suggestions를 숨기고, 아니면 보이고, 텍스트를 "Searching..."으로 채운다. 코드는 그대로 영어다.

3.2 fetch 한 줄 비동기

<button _="on click
            fetch /api/heavy
            put it into #result">
  Load
</button>

fetch /api/heavy 다음 줄의 it은 응답을 가리킨다. put it into #result — 응답을 #result에 넣어라. JavaScript Promise나 async/await의 이질감 없이 자연스럽게 흐른다.

3.3 자주 쓰이는 패턴

  • 토글 클래스: on click toggle .open on .menu
  • 컨퍼메이션: on click if confirm('Sure?') trigger submit on me
  • 타이머: on load wait 3s then add .visible to #toast
  • 드래그: on mousedown set $dragging to me on mousemove if $dragging then ...
  • 이벤트 위임: on click from .row in me ...

3.4 Hyperscript 채택 현실 — 2026년의 답

솔직해지자. Hyperscript는 HTMX만큼 채택되지 않았다. GitHub 스타는 HTMX의 1/10 수준이다. 그 이유는 단순하다.

  • VS Code 신택스 하이라이팅이 빈약하다 — 따옴표 안의 문자열로 인식되어 색이 나오지 않는다.
  • 에러 메시지가 부족하다 — 영어 문장이라 어디서 틀렸는지 추적이 어렵다.
  • TypeScript와 통합 안 됨 — 타입 안전성 없음.
  • 알파인이 더 친숙하다 — JavaScript 스타일 표현식이라 React/Vue 출신자가 적응 빠르다.

그래서 HTMX 사용자의 다수는 Hyperscript 대신 Alpine.js를 함께 쓴다. 그것이 다음 장이다.


4장 · Alpine.js — 반응성을 원하는 자를 위한 다리

Caleb Porzio가 만든 Alpine.js는 "Vue 3의 reactivity, jQuery의 직접성, Tailwind의 inline 접근"을 합친 라이브러리다. 15 KB. 의존성 zero. HTMX와 가장 잘 어울리는 보완재로 자리 잡았다.

4.1 기본

<div x-data="{ open: false }">
  <button @click="open = !open">Toggle</button>
  <div x-show="open" x-transition>Hello!</div>
</div>
  • x-data — 컴포넌트 스코프를 열고 reactive state 객체를 정의
  • @click (또는 x-on:click) — 이벤트 핸들러
  • x-show — 조건부 렌더링 (display 토글)
  • x-transition — 자동 트랜지션

이 다섯 개의 directive로 90%가 표현된다. 추가로 x-text, x-html, x-bind, x-model, x-for, x-if 등이 있다.

4.2 HTMX + Alpine — 황금 조합

HTMX는 서버 왕복, Alpine은 로컬 UI 상태. 둘은 충돌하지 않고 자연스럽게 합쳐진다.

<div x-data="{ mode: 'list' }">
  <button @click="mode = 'list'" :class="{ 'active': mode === 'list' }">List</button>
  <button @click="mode = 'grid'" :class="{ 'active': mode === 'grid' }">Grid</button>

  <div hx-get="/items" hx-trigger="load" hx-target="this">
    <!-- 서버에서 받은 HTML이 들어옴 -->
  </div>
</div>

뷰 모드 토글(클라이언트 상태)은 Alpine, 아이템 로드(서버 상태)는 HTMX. 각자 잘하는 일을 한다.

4.3 alpine-ajax — 동전의 반대편

흥미롭게도 Alpine 진영에는 alpine-ajax라는 서드파티 플러그인이 있다. Alpine 위에서 HTMX 같은 서버 통신을 한다. 이 도구의 존재 자체가 메시지다. HTMX 명제와 Alpine 명제는 같은 것을 다른 진입점에서 본 결과다.


5장 · Datastar — 차세대 하이퍼미디어 프레임워크

Delaney Gillilan이 만든 Datastar는 HTMX + Alpine을 한 라이브러리로 합치겠다는 도발적 시도다. 2024년에 1.0이 나왔고, 2026년 현재 1.3이 안정 버전. 사이즈는 ~10 KB.

5.1 한 가지 핵심 차이 — SSE 우선

HTMX는 일반 HTTP 응답을 받아 DOM을 교체한다. Datastar는 Server-Sent Events(SSE)를 1급 시민으로 둔다. 즉, 하나의 요청이 여러 개의 fragment 업데이트를 스트림으로 받는다.

<button data-on-click="@get('/calculate')">
  Calculate
</button>

<div id="step1"></div>
<div id="step2"></div>
<div id="final"></div>

서버는 단일 요청을 받고 SSE로 응답한다.

event: datastar-fragment
data: selector #step1
data: fragments <div id="step1">Step 1 done</div>

event: datastar-fragment
data: selector #step2
data: fragments <div id="step2">Step 2 done</div>

event: datastar-fragment
data: selector #final
data: fragments <div id="final">Result: 42</div>

LLM 스트리밍, 진행 상황 표시, 라이브 대시보드 — Datastar의 본진은 이런 곳이다. HTMX의 htmx-ext-sse 확장으로도 가능하지만, Datastar는 처음부터 SSE를 가정하고 설계되었다.

5.2 표현식 — Alpine 스타일

<input data-bind-name />
<div data-text="$name"></div>
<button data-on-click="$count++">+</button>
<p data-text="$count"></p>

data-bind-*, data-text, data-on-* — Alpine과 거의 호환되는 어휘. signal 기반 reactivity를 채용했고, 모든 state는 $로 접근한다.

5.3 Datastar vs HTMX — 어떻게 선택할까

  • LLM 스트리밍/실시간 대시보드 중심 → Datastar (SSE 1급)
  • 전통적 CRUD/폼 중심 → HTMX (생태계 성숙, 호환성 우월)
  • 클라이언트 사이드 상태가 적지 않음 → Datastar (Alpine 기능 내장)
  • 서버 백엔드와 잘 통합된 라이브러리 풍부 → HTMX (Django, Rails, FastAPI, Express 모두 헬퍼 있음)

2026년 현재 HTMX가 압도적 다수다. Datastar는 "SSE 네이티브"라는 강점을 LLM 시대에 들고 들어왔지만, 채택은 아직 얼리어답터 단계다.


6장 · Hypermedia Systems — 책 한 권이 만든 사상 체계

Carson Gross, Adam Stepinski, Deniz Akşimşek 세 사람이 쓴 『Hypermedia Systems』는 2023년에 출간되어 GitHub에서 오픈 액세스로 읽을 수 있다. 책의 부제는 "HTML, HTTP, and the Hypermedia Architecture"이다.

6.1 책이 답하는 질문들

  • 왜 우리는 JSON API + JavaScript 클라이언트 패턴을 기본값으로 받아들였는가?
  • HATEOAS가 정말 실용적인가, 아니면 학술적 호기심에 불과한가?
  • 모바일에서 하이퍼미디어가 통할 수 있는가? (책은 "Hyperview"라는 안드로이드/iOS 하이퍼미디어 클라이언트도 다룬다)
  • 하이퍼미디어로 만든 시스템은 어떻게 확장하는가?

6.2 LoB — Locality of Behavior 원칙

책이 도입한 핵심 원칙 중 하나가 Locality of Behavior(LoB, 행동의 지역성) 다. 한 문장으로 — "엘리먼트의 동작은 그 엘리먼트를 보면 알 수 있어야 한다."

React는 행동을 컴포넌트 트리 어딘가의 핸들러로 분리한다. CSS는 외부 파일에 둔다. JS 이벤트 위임도 행동을 다른 곳에 둔다. 이것이 좋다고 우리는 배워 왔지만(Separation of Concerns), Carson은 다른 원칙을 제안한다. 분리된 관심사가 아니라, 모인 행동.

<!-- 한 곳에서 행동이 보인다 -->
<button hx-post="/like"
        hx-target="#count"
        _="on click toggle .liked on me">
  Like
</button>

이 버튼이 무엇을 하는지 — POST 요청, 카운트 갱신, 클래스 토글 — 다른 파일을 열 필요가 없다. 디자이너도, 백엔드 개발자도, AI 어시스턴트도 이 한 줄을 보고 이해한다.

6.3 SoC vs LoB — 어느 쪽이 옳은가

답은 둘 다 옳고, 맥락이 결정한다. 큰 React 앱에서 컴포넌트 추상은 LoB의 결여를 보상한다. HTMX 앱에서는 화면이 작아 LoB가 그대로 통한다. 사용자가 5명인 내부 도구에서 LoB를 따르고, 1억 명을 향하는 SaaS에서는 컴포넌트 추상이 필요할 수 있다.

핵심은 — LoB는 기본값이 되어야 하고, 추상은 정당화되어야 한다는 것이다.


7장 · 프로덕션 사례 — 누가 실제로 쓰는가

HTMX는 장난감이 아니다. 2026년 현재 다음 사례들이 공개되어 있다.

  • GitHub — 새 PR 페이지의 일부, 이슈 코멘트 — Turbo 위주이지만 HTMX 영감 패턴이 다수
  • NASA JPL — 내부 도구 다수가 HTMX 기반(공개된 컨퍼런스 발표)
  • Contexte — 프랑스의 정치 뉴스 사이트, HTMX로 전체 리뉴얼
  • Replicant.au — 호주의 인디 SaaS, HTMX + Hyperscript 전면 사용
  • David Heinemeier Hansson37signals — Hotwire(Turbo + Stimulus)가 메인이지만, "프런트엔드 단순화" 진영의 대표 주자로 HTMX 진영과 사상을 공유
  • Bunny.net — CDN 회사, 관리자 대시보드 HTMX 기반
  • Quanta Magazine — 일부 인터랙티브 콘텐츠

DHH(David Heinemeier Hansson)는 HTMX 사용자는 아니지만, "복잡함은 패션이 아니다"라는 캠페인의 가장 큰 목소리다. 그의 Hotwire는 Rails 진영의 HTMX이고, 사상은 거의 같다. 2024-2025년 그가 한 일련의 토크 — "The One Person Framework", "What is Modern Web Development?" — 는 HTMX 진영의 자료로도 인용된다.

7.1 회사 규모는 어디까지 가능한가

소규모 SaaS, 내부 도구, 컨텐츠 사이트, 어드민 패널, CMS — 여기까지는 HTMX가 명백히 강하다. 모바일 앱처럼 동작해야 하는 PWA, 오프라인이 1급 시민인 협업 도구, 카운슬 of WebGL을 띄우는 클라이언트 헤비 게임 — 이런 곳에서는 HTMX가 약하다.

37signals의 Basecamp는 둘 사이 어딘가에 있다. 협업 도구이지만, 데이터 동기화 같은 무거운 클라이언트 상태는 적다.


8장 · 정직한 약점 — HTMX가 틀리는 곳

옹호문이 아니라고 했다. HTMX는 다음 영역에서 약하거나, 맞지 않는다.

8.1 풍부한 클라이언트 사이드 상태

  • Figma 같은 협업 화이트보드 — 수많은 객체의 상태를 클라이언트에서 추적해야 한다. 서버 왕복은 비현실적이다.
  • Notion 같은 블록 에디터 — 키 입력마다 서버에 가는 것은 불가능하다.
  • VS Code Web — 에디터 상태, 신택스 트리, LSP 응답 — 모두 클라이언트에서 처리되어야 한다.

이런 곳에서는 React/Vue/Solid가 옳다.

8.2 오프라인 우선

PWA, 모바일 우선, 비행기에서도 동작해야 하는 앱. HTMX는 서버 응답을 가정하므로 오프라인이 깨진다. Service Worker + IndexedDB + 동기화 큐 — 이 조합은 SPA의 영역이다.

8.3 복잡한 클라이언트 사이드 애니메이션

  • 60fps 캔버스 그림
  • 물리 시뮬레이션
  • 3D 인터랙션
  • 시간축 기반 비디오 편집

이런 곳에서 HTMX는 도울 게 거의 없다.

8.4 서버 부하

HTMX는 매 인터랙션마다 서버를 친다. 트래픽 패턴이 정적 자산 중심이 아니라 동적 HTML 생성 중심이 된다. 서버 측 캐싱, edge rendering, CDN 전략이 필수다. SPA에서는 이 부담의 일부가 CDN과 클라이언트로 옮겨갔지만, HTMX에서는 서버가 그것을 다시 받아야 한다.

8.5 모바일 네트워크

응답 시간이 200ms 이상 늘어지면 사용자 경험이 무너진다. Optimistic update 패턴이 어렵고, "버튼을 눌렀는데 뭐가 변한 게 없다"는 인상을 주기 쉽다.

해법은 있다. hx-disabled-elt, hx-indicator, 로컬 Alpine 상태로 즉시 UI 갱신 후 서버 응답으로 확정 — 그러나 "기본값으로 빠른" SPA보다는 신경을 더 써야 한다.


9장 · 결정 프레임워크 — 언제 HTMX인가

다음 표를 기준선으로 삼자. 모든 항목을 합산해 점수가 0보다 크면 HTMX, 0보다 작으면 SPA.

질문HTMX 점수SPA 점수
폼 중심인가?+20
콘텐츠 중심인가?+20
내부 도구/어드민인가?+20
팀이 5명 이하인가?+10
서버 언어가 강한가 (Django/Rails/Phoenix)?+20
SEO가 중요한가?+1-1
오프라인이 필요한가?-2+2
클라이언트 사이드 상태가 많은가?-2+2
60fps 애니메이션이 핵심인가?-2+2
모바일 앱과 코드 공유가 필요한가?-1+2
5만명 이상 동시 사용자인가?-1+1
디자이너가 HTML/CSS 우선인가?+10
기존 코드베이스가 React인가?-2+1

이 점수표는 절대값이 아니다. 그러나 시작점은 된다. 점수가 0 부근이면 — 두 패턴을 섞을 수 있다. HTMX로 큰 영역을 만들고, 진짜 SPA가 필요한 부분(에디터, 화이트보드)만 React island로 묶는 패턴이 갈수록 흔하다.

9.1 마이그레이션 전략

기존 React 앱이 있는데 HTMX로 옮기고 싶다면 — 전체 재작성은 거의 항상 잘못된 답이다. Strangler Fig 패턴을 쓰자.

  1. 새 페이지부터 HTMX로 작성 — 신규 어드민 페이지, 새 모듈
  2. CRUD가 단순한 화면을 표적 이전 — 폼 페이지, 리스트 페이지
  3. 에디터/대시보드 등 클라이언트 헤비 화면은 React로 유지 — island로 격리
  4. 공유 헤더/네비게이션은 서버 사이드 렌더링으로 — HTMX hx-boost로 전환 가속

10장 · 코드 한 페이지로 보는 HTMX 진영

10.1 HTMX — 인플레이스 편집 폼

<!-- 표시 모드 -->
<div hx-target="this" hx-swap="outerHTML">
  <p>제목: 안녕, 세상</p>
  <button hx-get="/posts/1/edit">편집</button>
</div>

<!-- 서버 응답: 편집 모드 -->
<form hx-put="/posts/1" hx-target="this" hx-swap="outerHTML">
  <input name="title" value="안녕, 세상" />
  <button type="submit">저장</button>
  <button hx-get="/posts/1">취소</button>
</form>

서버 측 의사 코드(Python/FastAPI):

@app.get("/posts/{id}/edit")
def edit_form(id: int):
    post = db.get_post(id)
    return templates.TemplateResponse("post_edit.html", {"post": post})

@app.put("/posts/{id}")
def update_post(id: int, title: str = Form(...)):
    db.update_post(id, title=title)
    post = db.get_post(id)
    return templates.TemplateResponse("post_display.html", {"post": post})

화면 전환 로직, 상태 머신, 폼 라이브러리 — 모두 사라졌다.

10.2 Hyperscript — 모달 닫기 한 줄

<div id="modal" class="hidden">
  <div class="overlay" _="on click hide #modal"></div>
  <div class="content">
    <button _="on click hide #modal">닫기</button>
    <p>Esc로도 닫힙니다.</p>
  </div>
</div>

<script>
  document.addEventListener('keydown', e => {
    if (e.key === 'Escape') document.getElementById('modal').classList.add('hidden')
  })
</script>

위 JavaScript도 Hyperscript로 한 줄에 적을 수 있다.

<body _="on keydown[key=='Escape'] from window hide #modal">

10.3 Datastar — LLM 스트리밍

<button data-on-click="@post('/chat', { messages: $messages })">
  Send
</button>

<div id="response"></div>

서버는 SSE로 토큰 단위 fragment를 보낸다.

event: datastar-fragment
data: merge inner
data: selector #response
data: fragments Hello

event: datastar-fragment
data: merge inner
data: selector #response
data: fragments Hello,

event: datastar-fragment
data: merge inner
data: selector #response
data: fragments Hello, world!

Datastar는 이런 사용 사례를 처음부터 가정한다. HTMX에서는 htmx-ext-sse 확장으로 비슷하게 가능하지만 1급 시민이 아니다.


에필로그 — 지루한 웹의 르네상스

웹의 처음 10년은 단순했다. HTML이 있었고, 폼이 있었고, 링크가 있었고, 서버가 있었다. 그러고 jQuery가 왔고, Ajax가 왔고, Backbone과 Angular가 왔고, React가 와서 — 우리가 만들던 모든 것이 갑자기 두 번 그려지기 시작했다. 한 번은 서버에서, 한 번은 클라이언트에서. 한 번은 Python으로, 한 번은 JavaScript로. 그리고 그 둘을 맞추는 데 점점 더 큰 비율의 에너지를 썼다.

Carson Gross는 그 어느 시점에 멈춰서 물었다. "우리가 정말 이 모든 것을 필요로 했는가?"

대답은 — 어떤 경우에는 그렇고, 어떤 경우에는 아니다. HTMX는 모든 곳에서 옳지 않다. 그러나 우리가 R React를 사용했던 곳의 절반은 React가 옳지 않았다. 그 절반의 절반은 — 어드민, CRUD, 사내 도구, 콘텐츠 사이트 — HTMX 한 줄로 충분했다.

2026년에 HTMX 진영이 강해지는 이유는 단순하다. AI 시대에 단순함은 더 가치 있는 자원이 되었다. LLM은 React 컴포넌트 트리 안의 상태 흐름을 추적하는 데 약하지만, <button hx-post="/like">가 무엇을 하는지 즉시 안다. AI 어시스턴트와 협업하는 코드베이스는 LoB가 우월하다.

지루한 웹은 끝난 게 아니다. 그저 잠시 잊혀졌다.

체크리스트 — 다음 프로젝트를 시작하기 전에

  • 폼 중심인가, 데이터 중심인가? 폼 중심이면 HTMX 먼저
  • 서버 언어와 강하게 통합되는가? Django/Rails/Phoenix면 HTMX가 자연스럽다
  • 오프라인이 핵심 요구사항인가? 그렇다면 SPA 또는 PWA
  • 60fps 캔버스/3D가 핵심인가? 그렇다면 SPA
  • 디자이너가 HTML/CSS로 직접 작업하는가? HTMX가 그들에게 친숙하다
  • 팀 규모가 5명 이하인가? 단순함의 ROI가 가장 높다
  • AI 어시스턴트와 짝 프로그래밍하는가? LoB 코드가 더 잘 이해된다

안티 패턴 — 하지 말 것

  • HTMX로 SPA 흉내내기 — 페이지 전환을 모두 hx-boost로만 처리하고 클라이언트 라우터를 만들지 않기. URL이 일관되게 작동하도록 서버 측 라우팅을 유지하라
  • 모든 인터랙션에 서버 왕복 — 클래스 토글 같은 순수 UI 동작은 Hyperscript/Alpine로 처리. 서버를 부를 필요 없는 곳에 부르지 말 것
  • HTML 조각이 거대해짐 — 응답 fragment는 작아야 한다. 페이지 절반을 매번 갱신한다면 SoC가 깨진 신호
  • Hyperscript와 Alpine 동시 사용 — 둘은 비슷한 영역을 다룬다. 하나만 선택하라
  • 테스트 부재 — HTMX는 서버를 더 자주 친다. 통합 테스트(Playwright 같은)가 더 중요해진다
  • 하이퍼미디어 가능성 무시한 JSON API 설계 — REST API를 JSON으로 설계하고 HTMX 페이지를 그 위에 얹으면 두 번 동기화하는 SPA와 같아진다. 하이퍼미디어를 1급 응답으로 두라
  • HTMX 확장을 모르고 시작htmx-ext-sse, htmx-ext-ws, htmx-ext-response-targets 등을 익혀라. 코어가 작은 만큼 확장이 자주 필요하다

다음 글 예고

다음 글에서는 HTMX + Django/FastAPI/Rails 백엔드 스택의 실전 구성 — 템플릿 조각 관리 전략, 인증 흐름, 파일 업로드, 폼 검증, CSRF, 그리고 "HTMX 친화적 컨트롤러 패턴" — 을 다룬다. 그리고 그 다음에는 HTMX와 React island를 한 페이지에 섞는 하이브리드 패턴으로 — Notion 스타일 에디터를 React로 띄우고 그 주변을 HTMX로 감싸는 실전 예제를 가져온다.


참고 / References

현재 단락 (1/305)

2026년 5월, 프런트엔드 개발자 한 사람의 평균적인 하루를 그려보자.

작성 글자: 0원문 글자: 16,317작성 단락: 0/305