12k
All articles

htmx 4.0 が登場

htmx 4.0は継承、エラーの置換、履歴、イベントを変更し、htmx 2アプリ向けの移行手順と戻し方も解説します。

OpenReplay Team
OpenReplay Team
htmx 4.0 が登場

htmx 4.0.0 は 2026 年 8 月 28 日にリリースされました。長年続いてきたいくつかのデフォルト挙動が変更されています。属性の継承はオプトイン方式になり、エラーレスポンスは DOM にスワップされ、履歴のスナップショットキャッシュは廃止されました。

htmx 2 のアプリを保守しているなら、実務上の関心は「2 年前にコンテナへ引き上げた hx-confirm はアップグレード後も何かを守ってくれるのか」でしょう。答えは「守りません」。修飾子を追加しない限りは。本記事では、何が壊れるのか、それぞれをどう元に戻せるのか、そして npm のリリース戦略のおかげでおそらく今週中に対応する必要はない理由を解説します。htmx とは何か、なぜハイパーメディアなのかについては、本記事の出発点となる htmx 2.0 のウォークスルーを参照してください。

要点

  • htmx 4 では属性の継承が :inherited 修飾子によって明示的になり、htmx.config.implicitInheritance を true に設定すれば htmx 2 の挙動が復元され、移行の橋渡しとして機能します。
  • htmx 4 でデフォルトでスワップをスキップするのは 204 と 304 のみになったため、サーバーレンダリングされた 422 は破棄されずにターゲットへ反映されます。htmx.config.noSwap = [204, 304, '4xx', '5xx'] で元に戻せます。
  • イベント名は htmx:phase:action のパターンに従うようになり、これには設定キーがありません。JavaScript 内のすべての htmx リスナーをリネームするか、htmx-2-compat 拡張を導入する必要があります。
  • アップグレード前に hx-disable を hx-ignore へリネームしてください。htmx 4 では hx-disable という名前が、従来 hx-disabled-elt が担っていた役割に再割り当てされているためです。
  • htmx 2.x が npm の latest タグを保持し、4.0 は next の下に置かれています。そのためバージョン指定のない CDN URL が強制的にアップグレードされることはなく、アナウンスでは htmx 2 を無期限にサポートすると明言されています。

htmx 4 で何が変わったのか?

htmx 4 はライブラリのリクエスト内部処理を XMLHttpRequest から fetch() へ移行しており、この書き換えが今回のリリースの他の変更を可能にしました。トランスポートの入れ替え自体がすでに破壊的変更だったため、チームは同じメジャーバージョンで htmx 1 以降に蓄積されてきたデフォルト設定をリセットすることにしたのです。

htmx を書く際にどちらの API も直接呼び出すことはないので、トランスポートの切り替え自体はテンプレート上では見えません。影響が現れるのは周辺部分です。XHR 固有のライフサイクルイベントには fetch() に対応するものがないため削除され、htmx 4 は htmx.config.defaultTimeout を 60000 に設定します(htmx 2 ではリクエストが無期限にハングし得ました)。

バージョン番号について。htmx の作者 Carson Gross は htmx 3 は決して出さないと述べていたため、今回のリリースは一足飛びに 4.0 となり、その約束は技術的には守られた形になりました。彼は 2025 年 11 月の書き換えを予告したエッセイでその理由を説明しています。

属性の継承は明示的になった

htmx 4 における継承は、こちらが要求したときにのみ発生します。コンテナ上の属性は、:inherited 修飾子を追加しない限りそのコンテナだけに適用されます。この修飾子は任意の属性で使えます: hx-boost:inherited、hx-target:inherited、hx-confirm:inherited。

<!-- htmx 4: the confirm reaches both buttons -->
<div hx-confirm:inherited="Are you sure?">
  <button hx-delete="/account">Delete My Account</button>
  <button hx-put="/account">Update My Account</button>
</div>

子要素の値は、デフォルトでは継承された値より優先されます。両者を結合したい場合は :append を使います。これは多くの人がつまずく合成のケースです:

<div hx-vals:inherited="tenant:acme">
  <button hx-post="/save" hx-vals:append="source:save-btn">Save</button>
</div>

:append がないと、ボタン自身の hx-vals が継承された値を置き換えてしまい、tenant はサーバーに届きません。祖先要素がその属性をまったく設定していない場合は、append された値だけが送信されます。名前は属性によって少し異なります。hx-disable のリファレンスページでは、親の値に追加する同じ役割として :merge が記載されています。

hx-inherit と hx-disinherit は削除されました。明示的なオプトインによって両方とも不要になったためです。テンプレートが旧来の挙動に依存している場合は、移行中の暫定措置として htmx.config.implicitInheritance を true に設定して復元できます。これはあくまで橋渡しであり、ゴールではないと考えてください。

エラーレスポンスがデフォルトでスワップされる

htmx 4 では、ステータスコードに関わらずレスポンスがターゲットに反映され、保留されるのは 204 と 304 のみです。サーバーレンダリングされた 422 のバリデーションページは、黙って破棄されるのではなくターゲットに反映されるようになりました。これはハイパーメディアアプリがずっと求めていた挙動です。HTTP エラーレスポンスは htmx:response:error イベントも発火します。

新しい hx-status 属性は、個々のコードを専用のターゲットとスワップへルーティングします:

<form hx-post="/submit"
      hx-target="#result"
      hx-status:422="target:#validation-errors"
      hx-status:5xx="target:#server-error"
      hx-status:503="swap:none">
  <input name="email">
  <button type="submit">Submit</button>
</form>

htmx はもっとも具体的なパターンから順に試します。まず正確なコード、次に末尾 1 桁をマスクしたパターン(50x など)、最後に末尾 2 桁をマスクしたパターン(5xx など)です。属性値の中では swap:、target:、select:、push:、replace:、transition: を指定できます。

バックエンドがスワップされることを想定していないエラーページを返す場合は、htmx.config.noSwap を [204, 304, '4xx', '5xx'] に設定すれば htmx 2 の挙動に戻ります。

戻る操作は実際のリクエストになった

htmx 4 は、htmx 2 で履歴を支えていたクライアントサイドの DOM スナップショットキャッシュを廃止しました。戻るボタンを押すと、htmx はサーバーに再度ページを要求し、返ってきた内容を <body>(ページに [hx-history-elt] 要素があればそちら)にスワップします。

実務上の効果として、戻るボタンで表示されるのは離脱時点で凍結されたスナップショットではなく、サーバーが現時点で示すページの内容になります。これにより、サードパーティスクリプトが DOM を書き換え、復元されたスナップショットがその変更を再生して壊れた状態になる、という一連のバグが解消されます。同時に、戻る操作にリクエストのコストがかかることも意味します。

hx-history 属性はキャッシュとともに廃止されました。スナップショットが必要な場合は、hx-history-cache コア拡張がオプトインとして再導入します。

イベント名は htmx:phase:action パターンに従う

htmx のすべてのライフサイクルイベントが htmx:phase:action[:sub-action] の形にリネームされました。アナウンスでは htmx:beforeRequest が htmx:before:request に、htmx:beforeSwap が htmx:before:swap になると示されています。htmx:afterSwap は htmx:after:swap になります。

これは設定による回避手段がない唯一の変更です。すべてのリスナーを編集する必要があります:

// htmx 2
document.body.addEventListener('htmx:afterSwap', (e) => {
  initTooltips(e.detail.target);
});

// htmx 4
document.body.addEventListener('htmx:after:swap', (e) => {
  initTooltips(e.detail.target);
});

ほとんどのエラーイベントは htmx:error に統合され、HTTP エラーレスポンスは htmx:response:error を発火します。XHR 固有のイベントは単純になくなりました。fetch() に相当するものが存在しないためです。リスナーの手作業による書き換えが移行作業の大半を占めるなら、htmx-2-compat 拡張が旧イベント名を新しい名前にマッピングし、暗黙的な継承と hx-ext も復元します。

htmx 4 で壊れたものではなく、新しく加わったものは?

htmx 4 の 3 つの追加機能は、それだけでアップグレードする価値があります。morph スワップ、<hx-partial> 要素、そして書き直されたストリーミング拡張です。morph スワップはコアに入ったため、状態を保持する DOM 更新に拡張は不要になりました。<hx-partial> 要素は、1 つのレスポンスで複数のターゲットを更新でき、それぞれが独自のターゲットとスワップを持ちます:

<hx-partial hx-target="#messages" hx-swap="beforeend">
  <div>New message</div>
</hx-partial>
<hx-partial hx-target="#count">
  <span>5</span>
</hx-partial>

ターゲットとスワップの方式が partial 自体に付与されるため、マークアップ中に散らばった hx-swap-oob 属性から挙動を読み解く必要はなく、レスポンス側が各パーツに対する処理内容を明示します。なお htmx 4 では out-of-band の順序が逆転し、メインコンテンツが先にスワップされる点に注意してください。

もう 1 つの目玉はストリーミング拡張です。SSE と WebSocket の両拡張が今回のリリースに向けて再構築され、それと並んで新しい拡張群が提供されます: hx-multipart、hx-live、hx-targets、hx-ptag、hx-csp、hx-download、hx-prompt、hx-history-cache。接続用の属性は名前空間化されており、SSE は hx-sse:connect、WebSocket は hx-ws:connect で接続します。

htmx 4 へのアップグレードと、急ぐ必要がない理由

htmx 4 へのアップグレードはスキャナーから始めます。計画を立てる前に実行してください。npx htmx.org@4.0.0 upgrade-check -- ./path/to/project/root はプロジェクトを走査し、非推奨パターンをファイル名と行番号付きで出力します。これだけで作業量を半日ほどで見積もれます。

npx htmx.org@4.0.0 upgrade-check -- ./templates
npx htmx.org@4.0.0 upgrade-check --ext .vue ./path/to/project/root

デフォルトでは .html、.php、.js、.ts、.jinja、.jinja2、.j2、.erb、.hbs を対象とします。単一ファイルコンポーネント形式はこのセットに含まれないため、--ext を渡さない限り .vue、.svelte、.jsx、.astro のテンプレートはチェックされません。

他の作業に手をつける前に、1 つだけリネームを済ませてください: hx-disable は hx-ignore になり、hx-disabled-elt は hx-disable になります。 旧名が別の役割に再利用されているため、先に hx-disabled-elt を移行すると、まだ htmx 2 の意味を持つ属性を上書きしてしまいます。

変更点htmx 4 のデフォルトhtmx 2 に戻す方法
属性の継承:inherited による明示的な指定htmx.config.implicitInheritance = true
エラーレスポンスのスワップ204/304 のみスワップをスキップhtmx.config.noSwap = [204, 304, '4xx', '5xx']
履歴戻る操作でサーバーへ再取得hx-history-cache 拡張
イベント名htmx:phase:action設定キーなし。htmx-2-compat 拡張

そして、これらが急を要するかどうかを決定づける部分です。npm では htmx 2.x が latest dist-tag を保持し、4.0.0 は next の下に公開されています。アナウンスではこれが意図的であることが明言されており、バージョン指定のない CDN URL から htmx を読み込んでいるサイトが破壊的変更へ強制的にアップグレードされないようにするためで、2.x は 2027 年初頭まで latest に留まります。2.x は無期限にサポートされます。

これは 4 つの状況に対応します。バージョン指定のない CDN URL は、タグが切り替わるまで 2.x を配信し続けます。将来の期限が付いているのはこのケースだけです。バージョン固定の CDN URL と npm の厳密なバージョン固定は、自動的に変わることはありません。^2.0.0 のような npm のレンジ指定は、dist-tag に関わらず 2.x の範囲内に留まります。今日 4.0 をインストールするには、バージョンを固定してください: npm install htmx.org@4.0.0、またはバージョン付きの CDN パスを使います。

新規プロジェクトは 4.0 で始めましょう。既存の htmx 2 アプリの場合は、スキャナーを実行し、まず hx-disable のリネームを行い、レポートの長さを見て今移行するか、dist-tag が移動する前に再検討するかを判断してください。

FAQ

hx-ext が削除された htmx 4 では、どうやって htmx 拡張を読み込むのですか?

htmx のスクリプトの後に拡張スクリプトを含めれば、その属性はすぐに機能し、有効化のための属性は不要です。dist/ext/hx-sse.js を htmx.min.js と並べて読み込めば、hx-sse:connect を直接使えます。htmax.js ディストリビューションは、htmx と主要な拡張を単一ファイルにバンドルして提供しており、それらの属性が自動的に利用可能になります。拡張の作者は、名前とメソッドマップを指定して htmx.registerExtension 経由で登録します。

htmx 4 が戻る操作時に行う追加のサーバーリクエストを避けられますか?

はい。hx-history-cache コア拡張は、完全なサーバーリクエストを発行するのではなく sessionStorage から履歴を復元します。これが htmx 2 のスナップショットにもっとも近い代替です。代わりに 2 つの設定値で挙動を変えることもできます。htmx.config.history を 'reload' にすると履歴ナビゲーション時にページ全体をリロードし、htmx.config.history を false にすると履歴の処理を無効化します。htmx 2 の localStorage スナップショットキャッシュは廃止されています。

htmx 4 では hx-vars と hx-prompt の代わりに何を使いますか?

hx-vars は削除され、計算値は js: プレフィックスを付けた hx-vals へ移行します。hx-prompt はコアから削除され、拡張として提供されます。hx-prompt 拡張を読み込めば同じ構文を維持できます。その他に削除された属性には hx-ext、hx-inherit、hx-disinherit、hx-history があります。hx-disabled-elt は削除ではなくリネームで、hx-disable になり、旧来の hx-disable は hx-ignore になります。これは [What's New in htmx 4](https://four.htmx.org/docs/whats-new-in-htmx-4) のリネーム表に記載されているとおりです。upgrade-check スキャナーはこの 2 つを renamed-attr、実際の削除を removed-attr として、それぞれファイル名、行番号、推奨される置き換え先とともに示します。

htmx 4 でも hx-swap-oob は機能しますか。また hx-partial はどんなときに使うべきですか?

hx-swap-oob は引き続き機能しますが、htmx 4 は順序を逆転させました。メインコンテンツが先に入り、out-of-band と hx-partial の要素がドキュメント順でそれに続きます。同じ要素を更新後のコピーに差し替える場合は hx-swap-oob を、1 つのレスポンスで複数の箇所を更新する必要がある場合は hx-partial を選んでください。後者では、マークアップ中に散在する属性に頼るのではなく、各 partial が自身の hx-target と hx-swap を明示します。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.