htmx 4.0.0がリリース — XHRからfetch()への移行と「属性継承が明示に変わった」破壊的変更を解説

はじめに

2026年8月28日、htmx が 4.0.0 をリリースした。Hacker News で 260pt、r/programming でも上位に上がり、SPA疲れの文脈で語られることの多いこのライブラリのメジャー更新に相応の注目が集まっている。

htmx は「JavaScript をほとんど書かず、HTML の属性だけでサーバとのやり取りとDOM更新を表現する」ライブラリだ。今回の 4.0.0 は、その使用感を支えていた内部実装を XMLHttpRequest から fetch() に全面移行したうえで、長年の設計上の負債をまとめて清算するリリースになっている。

この記事では3点を扱う。なぜ 3 を飛ばして 4 なのか。fetch() 移行が何を可能にし、何を壊したのか。そして 2.x を使っている既存プロジェクトが、移行時にまず確認すべき破壊的変更はどこか。

なぜ「htmx 3」は存在しないのか

まず小ネタから入るが、これが今回のリリースの性格をよく表している。

htmx の作者 Carson Gross は以前、「htmx 3 は出さない」と公言していた。ライブラリが安定期に入り、これ以上メジャーバージョンを重ねる必要はないという立場だった。ところが今回、fetch() 移行という互換性に波及する変更が必要になり、メジャーバージョンを上げざるを得なくなる。

そこで取られたのが「3 を飛ばして 4 にする」という手だった。3 を出さないという約束は技術的に守られる、というジョークである。バージョン番号にジョークを持ち込めるあたりが htmx というプロジェクトの空気なのだが、裏を返せば「メジャーバージョンを上げるのは本来やりたくなかった」という認識の表明でもある。

実際、公式アナウンスは移行を急かしていない。

htmx 2 will continue to be supported indefinitely so don’t feel any pressure to upgrade. (htmx 2 は無期限にサポートを継続するので、アップグレードのプレッシャーを感じる必要はない)

さらに npm 上でも 4.0.0 は latest ではなく next タグとして公開されており、latest への昇格は 2027年初頭が予定されている。バージョン指定なしで CDN から読み込んでいるサイトが、ある朝いきなり壊れることのないようにという配慮だ。破壊的変更を含むメジャーリリースの配布方法として、かなり慎重な部類に入る。

fetch() 移行が解いたもの、生んだもの

技術的な中核は XMLHttpRequest から fetch() への移行だ。作者はこれを “The fetch()ening” というエッセイで事前に説明している。

XHR が残っていたのは htmx 1.0 時代の IE サポートの名残りで、モダンブラウザだけを相手にする現在では純粋な負債になっていた。問題はサイズよりもコールバックベースの制御フローにある。エッセイ内の表現を借りれば、fetch() への移行は非同期コードを線形化し、try/catch のような言語標準の機能を使えるようにすることで、htmx のイベントモデルをより予測可能にする。

そしてこの「複雑さの削減」で浮いた予算が、新機能に再投資されている点が今回の面白いところだ。

1つめは morph スワップのコア取り込み。 これまで拡張(Idiomorph)として提供されていた DOM 差分マージが、morphInner / morphOuter としてコアに入った。従来の innerHTML 置換ではフォーカス位置、テキスト入力途中の値、スクロール位置、CSS トランジションの状態などが吹き飛んでいたが、morph は2つの HTML を比較して最小限の変更だけを適用する。React の再調整に近い発想を、サーバから返る HTML 断片に対してやる形になる。hx-morph-skip / hx-morph-skip-children で「ここは触るな」という指定も可能だ。

2つめはストリーミング。 fetch() の ReadableStream を扱えるようになったことで、レスポンスを最後まで待たずに、届いた HTML 断片から順次 DOM に差し込める。SSE(hx-sse)、WebSocket(hx-ws)、multipart(hx-multipart)の各拡張がこの基盤に乗る形で更新された。LLM の応答をストリーム表示するような用途が、フレームワークなしで素直に書けるようになる。

一方、XHR に紐づいていたものは落ちた。htmx:xhr:* 系のイベントは丸ごと削除されている。アップロード進捗のようにXHR固有のAPIに依存していた実装は、移行時に個別の対応が必要になる。

移行で最初に踏むのは「属性継承」

既存プロジェクトの移行で最大の落とし穴は、fetch() ではなく属性継承モデルの変更だ。

htmx 2 まで、hx-target や hx-confirm といった属性は親要素から子要素へ暗黙的に継承されていた。親の <div> に hx-confirm を書けば、その中の全ボタンに確認ダイアログが付く。便利ではあるが、「このボタンの挙動を知るには DOM ツリーをルートまで遡らないと分からない」という状態を生む。それを打ち消すための hx-disinherit という属性まで存在していた。

htmx 4 では、これが反転する。

In htmx 4 attributes are not inherited unless you explicitly say so by adding an :inherited after the attribute name (htmx 4 では、属性名の後ろに :inherited を付けて明示しない限り、属性は継承されない)

つまり hx-confirm="..." は自分の要素にしか効かず、子孫に効かせたいなら hx-confirm:inherited="..." と書く。htmx が掲げてきた locality of behavior(挙動の局所性) — 要素の振る舞いはその要素を見れば分かるべき — という原則を、継承の既定値にも適用した形だ。役目を終えた hx-disinherit は削除された。

これは静かに壊れるタイプの変更である点に注意したい。継承前提で書かれたテンプレートを 4 に上げても、構文エラーは出ない。確認ダイアログが出なくなる、ターゲットが外れて意図しない場所にスワップされる、といった挙動の差として現れる。

そのため公式は移行チェック用のCLIを用意している。

npx htmx.org@4.0.0 upgrade-check -- ./templates

テンプレートディレクトリを走査して、非互換になりうる箇所を検出してくれる。手作業の grep で拾える範囲を超えるので、移行を検討するなら最初に流すべきコマンドだ。

その他の主な破壊的変更も挙げておく。

  • イベント名の体系化: htmx:beforeRequest → htmx:before:request、htmx:afterSwap → htmx:after:swap のように htmx:フェーズ:アクション 形式へ統一
  • hx-vars の削除: hx-vals の js: プレフィックスに一本化
  • hx-prompt のコア外し: 拡張として提供
  • 履歴エンジンの廃止: localStorage に DOM スナップショットを保存する仕組みは壊れやすかったため撤去され、標準的なページリロードに戻された
  • 空レスポンスの扱い: swapEmpty 修飾子が追加され、SSE は既定で空レスポンスをスワップしなくなった。キープアライブの空フレームで表示が消える事故を防ぐ変更で、従来動作が必要なら hx-swap="innerHTML swapEmpty:true" を指定する
  • <hx-partial> タグの追加: out-of-band スワップの代替。<hx-partial hx-target="#messages" hx-swap="beforeend"> のように、1レスポンスで複数箇所を更新する意図が読み取りやすくなる

エンジニアへの影響

実務目線での結論はシンプルだ。

新規プロジェクトなら 4.0 を選ぶ理由がある。 morph がコアに入ったことは体感差が大きい。フォームを含む領域を部分更新するとき、2.x では入力中の値やフォーカスが飛ぶのを避けるために OOB スワップで対象を細切れにする設計が必要だった。morph ならその設計コストをかなり削れる。ストリーミングも同様で、LLM 応答の逐次表示のような要件をフレームワーク導入なしで満たせるのは、管理画面や社内ツールでは効いてくる。

既存の 2.x プロジェクトは急がなくていい。 無期限サポートが明言されており、npm の latest も 2027年初頭まで 2.x のままだ。動いているものを慌てて上げる合理的理由は薄い。移行するなら upgrade-check を流し、属性継承とイベント名の2点に絞って影響範囲を見積もるところから始めるのが順当だろう。テンプレート量が多いプロジェクトほど、継承の書き換えが工数の大半を占めるはずだ。

もう一段引いて見ると、今回のリリースは「ブラウザが標準で持つ機能が増えたので、ライブラリ側は薄くできる」という流れの一例でもある。fetch() と ReadableStream が当たり前に使える前提に立ったから、XHR 時代の互換コードを捨てて morph とストリーミングを入れる余地が生まれた。ブラウザの進化をライブラリの削減に還元するという方向性は、htmx に限らず今後のフロントエンドライブラリで増えていく判断だと思う。

まとめ

  • htmx 4.0.0 がリリース。作者の「3 は出さない」という宣言を守るため、バージョン 3 は飛ばされた
  • 内部実装が XMLHttpRequest から fetch() へ全面移行。非同期処理が線形化されイベントモデルが予測しやすくなった
  • 浮いた複雑さの予算で、morph スワップ(Idiomorph)のコア取り込みとReadableStream によるストリーミングを獲得
  • 最大の破壊的変更は属性継承の明示化。:inherited を付けない限り継承されず、構文エラーではなく挙動差として現れるため npx htmx.org@4.0.0 upgrade-check の実行が事実上必須
  • htmx 2 は無期限サポート、npm の latest 昇格も2027年初頭。移行を急ぐ必要はない

SPA 全盛から揺り戻しが語られる中で、htmx はハイパーメディア路線を維持したまま、ブラウザ標準の進化を素直に取り込む形でメジャー更新を果たした。morph とストリーミングがコアに入った 4.x が、これまで「htmx では厳しい」とされてきた要件の線をどこまで動かすかが、今後1年の見どころになる。

今日のその他のニュース

GLM-5.3 がオープンウェイトで公開 — Z.ai(智譜)の GLM-5.3 が Hugging Face 上でオープンウェイト公開され、Hacker News で 336pt を集めた。フロンティア級モデルのオープンウェイト公開が続く流れの中でも注目度が高い。同日には「MacBook Pro 128GB でローカルLLMがついに実用になった」という検証記事も上がっており、手元のワークステーションで実務が回る閾値がまた動いている。

HTTPX2 — Python向け次世代HTTPクライアント — Pydantic チームによる httpx の後継実装が公開(HN 100pt)。同時に OpenAI Python SDK の HTTPX2 移行ドキュメントも 166pt を集めており、主要SDKが実際に移行を始めている。Python で LLM API を叩いているなら、依存の HTTP クライアント層が動く可能性がある。

Luanti、根拠のないAI著作権通知で Google Play から削除 — OSSゲームエンジン Luanti が、AI による自動生成とみられる DMCA テイクダウン通知で Play ストアから削除された(HN 314pt)。AI 生成の権利侵害通知が実際にストア掲載を止めうることを示した事例で、個人・小規模でアプリを配信している立場には他人事ではない。

ソース