LabHub

ブログ

HTMX & Hyperscript — 反-SPA陣営の2026年、Carson Grossが投げかけたハイパーメディア命題 (2026 深掘り)

한국어English日本語

プロローグ — SPA疲れ、そしてCarsonの一言

2026年5月、あるフロントエンド開発者の平均的な一日を描いてみる。

pnpm installの終わりを待ちながらコーヒーを淹れ直し、Vite devサーバを立ち上げ、React Queryのキャッシュ無効化方針を書き直す。昨日まで動いていたビルドが今日はModule not found: Can't resolve 'crypto'で落ちる。誰かがnext.config.jsにwebpackポリフィルを足したら、今度はTurbopackが怒る。フォーム1つ作るのに — useStateuseEffectuseMutationzodスキーマ、react-hook-formリゾルバ、楽観更新ロジック、エラーバウンダリ、ローディングスピナー — 7つの抽象が動員される。そのフォームが結局やるのはPOST /todosと画面の一部の更新だ。

この見慣れた風景に、一人の人物がナイフを突き立てた。モンゴメリ・カレッジ計算機科学科の教授、Carson Gross。彼は2020年にintercooler.jsを書き直したライブラリをHTMXと名付け、時代を一文で要約した。

「HTMLはすでにハイパーメディアだ。私たちがしたのは、その潜在能力を11 KBで解放しただけだ」

HTMXは新しいフレームワークではない。HTMLに4つの属性を追加しただけだ。hx-gethx-posthx-swaphx-target。フォームはもうJavaScriptハンドラを必要としない。ボタンが直接サーバにPOSTを送り、サーバはHTMLの断片を返し、その断片がページの一部を置き換える。それだけだ。

しかしその単純さは単なる構文以上の何かだ。Carsonはその上に一冊の書籍を載せた。『Hypermedia Systems』 — Roy FieldingのREST論文を読み直し、HATEOASを真剣に受け取り、「なぜ私たちはJSON APIを作り、その上にクライアントサイドルータを作り、その上に状態管理を作り、その上にキャッシュを作り、その上に最適化コンパイラを作ったのか」を問う書籍だ。答えは挑発的だ。「必要なかった」

本稿はHTMX陣営 — 反-SPA陣営、つまり「boring web(退屈なWeb)」ルネサンス — の2026年を整理する。HTMX 2.xのコア、_hyperscriptというCarsonの二作目、Datastarという次世代候補、Alpine.jsという妥協案、『Hypermedia Systems』が提示した思考フレーム、37signalsのような本番事例、そして — もっとも重要なこと — HTMXが間違うところまで。

これはHTMX擁護論ではない。いつHTMXが正しく、いつReactが正しいかを判断するために必要なすべての道具を一息で提供することが目的だ。


1章 · ハイパーメディア命題 — Roy Fieldingを読み直す

HTMXのすべての主張は1冊の博士論文から始まる。2000年、Roy Fieldingの「Architectural Styles and the Design of Network-based Software Architectures」 — 我々が略してRESTと呼ぶものだ。

FieldingはRESTに6つの制約を置いた。クライアント-サーバ、無状態、キャッシュ可能、統一されたインターフェース、階層化されたシステム、そして — HATEOAS: Hypermedia As The Engine Of Application State。

最後の制約が核だ。HATEOASは「クライアントは次の行動についての情報を、サーバが応答で返したハイパーメディアからのみ取得すべきだ」と述べる。つまり応答に「次に何ができるか」という情報がリンクとフォームの形で入っている必要があるということだ。

Carsonの診断はこうだ。

HTMXの命題は故に単純だ。

「ブラウザはすでに優秀なハイパーメディアクライアントだ。HTMXはそのクライアントの語彙を拡張する」

素のHTMLは「リンククリック」または「フォーム送信」時のみサーバリクエストが起こる。HTMXはそれを拡張する。

これがHTMXのすべてだ。一行で要約すると — 「any element, any event, any HTTP verb, any swap target」


2章 · HTMX 2.xコア — 4つの属性で99%を作る

2024年1月、HTMX 2.0がGAになった。2025年に2.1、2026年初に2.2がリリースされ、現在の安定版はhtmx.org基準で11.4 KB(gzipped)。依存関係はゼロ。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で「タイプするとライブで結果が更新される検索」を作るには — useStateuseDeferredValue、debounceフック、キャッシュ無効化、abortコントローラ、ローディング状態マシン — 最低でも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>

ここで起こることを一行ずつ読んでみる。

サーバは? 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、レース条件、キャッシング — このすべてをHTMXが処理する。 我々はビジネスロジックだけを書く。

2.3 コア属性13個

HTMXの表面積は小さい。13個のコア属性でほぼすべてが表現される。

属性役割
hx-get / hx-post / hx-put / hx-patch / hx-deleteHTTPリクエスト発射
hx-triggerどのイベントでトリガーするか(clickinputevery 2srevealed、...)
hx-target応答をどの要素に適用するか(CSSセレクタ)
hx-swapどう適用するか(innerHTMLouterHTMLbeforebeginafterend、...)
hx-valsリクエストに追加する値(JSONまたはJS関数)
hx-headersカスタムヘッダ
hx-indicatorリクエスト中インジケータ
hx-confirm送信前のconfirm()ダイアログ
hx-push-url応答を受けたときのブラウザURL更新
hx-boost通常のリンク/フォームを自動でAJAXにアップグレード

hx-boostだけでも興味深い。<body hx-boost="true">一行を追加すると、ページのすべてのaformが自動でAJAXリクエストを送り、応答からbody部分だけを置換する。TurboやInertiaと似た効果を1属性で

2.4 hx-swapの威力 — DOM操作が宣言的になる

hx-swapは応答HTMLをDOMに適用する方法を決める。

ここにhx-swap="outerHTML swap:1s settle:1s scroll:#top"のようなmodifierを付けると — swap前に1秒待機(アニメーション)、settleフェーズ1秒(遷移クラス適用)、完了後#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なしでもポーリングで十分な領域 — 通知、株価、ライブカウンタ — で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 よく使われるパターン

3.4 Hyperscript採用の現実 — 2026年の答え

正直になろう。HyperscriptはHTMXほどに採用されていない。GitHubスターはHTMXの1/10程度だ。理由は単純だ。

そのため大多数のHTMXユーザはHyperscriptではなくAlpine.jsを併用する。それが次の章だ。


4章 · Alpine.js — リアクティビティを欲しがる者への橋

Caleb Porzioが作ったAlpine.jsは「Vue 3のリアクティビティ、jQueryの直接性、Tailwindのインラインアプローチ」を合わせたライブラリだ。15 KB。依存関係ゼロ。HTMXとの最良の補完として定着した。

4.1 基本

<div x-data="{ open: false }">
  <button @click="open = !open">Toggle</button>
  <div x-show="open" x-transition>Hello!</div>
</div>

この5つのdirectiveで90%が表現される。追加でx-textx-htmlx-bindx-modelx-forx-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を1ライブラリに合わせる挑発的試みだ。2024年に1.0が出て、2026年現在1.3が安定版。サイズは約10 KB。

5.1 一つの核心的差異 — SSE優先

HTMXは通常のHTTP応答を受けてDOMを置換する。DatastarはServer-Sent Events(SSE)を1級市民に置く。つまり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-textdata-on-* — Alpineとほぼ互換の語彙。シグナルベースのリアクティビティを採用し、すべての状態は$でアクセスする。

5.3 Datastar vs HTMX — どう選ぶか

2026年現在HTMXが圧倒的多数だ。Datastarは「SSEネイティブ」という強みをLLM時代に持ち込んだが、採用はまだアーリーアダプタ段階だ。


6章 · Hypermedia Systems — 一冊が作った思想体系

Carson Gross、Adam Stepinski、Deniz Akşimşekの3人が書いた『Hypermedia Systems』は2023年に出版され、GitHubでオープンアクセスで読める。副題は「HTML, HTTP, and the Hypermedia Architecture」。

6.1 書籍が答える問い

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年現在、次の事例が公開されている。

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級市民である協業ツール、WebGLキャンバスをいくつも立ち上げるクライアント重めのゲーム — こういう場所ではHTMXは弱い。

37signalsのBasecampは両者の間のどこかにある。協業ツールだがリアルタイム同期のような重いクライアント状態は少ない。


8章 · 正直な弱点 — HTMXが間違うところ

擁護論ではないと言った。HTMXは次の領域で弱い、または合わない。

8.1 豊富なクライアントサイド状態

こういう場所ではReact/Vue/Solidが正しい。

8.2 オフラインファースト

PWA、モバイル優先、飛行機でも動作すべきアプリ。HTMXはサーバ応答を仮定するのでオフラインが壊れる。Service Worker + IndexedDB + 同期キュー — この組み合わせはSPAの領域だ。

8.3 複雑なクライアントサイドアニメーション

こういう場所でHTMXは役に立つことがほぼない。

8.4 サーバ負荷

HTMXはインタラクションごとにサーバを叩く。トラフィックパターンが静的アセット中心から動的HTML生成中心に変わる。サーバサイドキャッシング、エッジレンダリング、CDN戦略が必須だ。SPAではこの負担の一部がCDNとクライアントに移っていたが、HTMXではサーバが再び引き受ける。

8.5 モバイルネットワーク

応答時間が200ms以上に延びるとユーザ体験が崩壊する。Optimistic updateパターンが難しく、「ボタンを押したのに何も変わらない」という印象を与えやすい。

回避策はある。hx-disabled-elthx-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島に閉じ込めるパターンが増えている。

9.1 マイグレーション戦略

既存のReactアプリがあってHTMXに移したいなら — 全面書き直しはほぼ常に間違った答えだ。Strangler Figパターンを使おう。

  1. 新ページからHTMXで書く — 新規管理ページ、新モジュール
  2. CRUDが単純な画面を標的移行 — フォームページ、リストページ
  3. エディタ/ダッシュボードなどクライアント重い画面はReactで維持 — 島として隔離
  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級市民ではない。


エピローグ — 退屈なWebのルネサンス

Webの最初の10年は単純だった。HTMLがあり、フォームがあり、リンクがあり、サーバがあった。そしてjQueryが来て、Ajaxが来て、BackboneとAngularが来て、Reactが来て — 我々が作るものすべてが急に二度描かれ始めた。一度はサーバで、一度はクライアントで。一度はPythonで、一度はJavaScriptで。そしてその二つを合わせることに、次第に大きな割合のエネルギーを使うようになった。

Carson Grossはどこかの時点で立ち止まり、問うた。「我々は本当にこのすべてを必要としていたのか?」

答えは — ある場合はそうだし、ある場合は違う。HTMXはすべての場所で正しいわけではない。しかし我々がReactを使った場所の半分はReactが正しくなかった。その半分のさらに半分 — 管理、CRUD、社内ツール、コンテンツサイト — はHTMX一行で十分だった。

2026年にHTMX陣営が強くなる理由は単純だ。AI時代に単純さはより価値ある資源になる。 LLMはReactコンポーネントツリーの中の状態の流れを追うのに弱いが、<button hx-post="/like">が何をするかは即座に分かる。AIアシスタントと協業するコードベースはLoBが優位だ。

退屈なWebは終わったのではない。ただしばらく忘れられていただけだ。

チェックリスト — 次のプロジェクトを始める前に

アンチパターン — してはいけないこと

次回予告

次回はHTMX + Django/FastAPI/Railsバックエンドスタックの実戦構成 — テンプレートfragment管理戦略、認証フロー、ファイルアップロード、フォーム検証、CSRF、そして「HTMXフレンドリーなコントローラパターン」 — を扱う。その次にはHTMXとReact島を一ページに混ぜるハイブリッドパターンで — Notion風エディタをReactで立てその周りをHTMXで包む実戦例を持ってくる。


参考 / References

コメント

まだコメントはありません。

ログインするとコメントできます