データ領域
データ領域は、自身の更新ライフサイクルを所有する UI の一部です。 ページ描画時に一度読み込み、その後はポーリング、イベントの待ち受け、 またはその組み合わせで自身を最新に保ちます。コンポーネントはただの htmx 駆動コンテナで、ビヘイビアヘルパーは不要です。
別名: 自動更新、ポーリング。
Simulate a change elsewhere を押してください — エンドポイントは
204 と HX-Trigger: {"items:changed":{}, "hc:toast":…} で応答し、
イベントを聞いた領域が自分自身を再取得します(Rendered at の時刻が
変わるのが見えます)。エンドポイントはサーバレスポンス契約を実装した
名前空間付きデモで、api/recipes/data-region/ 配下にあります。
2 つ目の領域(Polling region)は
スケジュールによるポーリングのデモ
です: 10 秒ごとに自身を再取得します — 再描画されるフラグメントは
every 10s だけを持ち、htmx がスワップのたびにインターバルを
再アームします。Rendered at の行が刻むのを見てください。
Items
Polling region
スケジュールによるポーリング
Section titled “スケジュールによるポーリング”<section id="orders-summary" class="hc-data-region" data-hx-get="/orders/summary" data-hx-trigger="load, every 10s" data-hx-swap="outerHTML" data-hx-indicator="closest .hc-data-region" aria-busy="false"> <header class="hc-data-region__header"> <h2>Orders</h2> <span class="hc-spinner htmx-indicator" aria-hidden="true"></span> </header> <p class="hc-field__message">Loading…</p></section>構成要素:
data-hx-trigger="load, every 10s"— ページロード時に一度描画し、 以後 10 秒ごとに再描画。サーバが再描画するフラグメントはevery 10sだけを持ちます: htmx はスワップのたびに インターバルを再アームし(その再スタートこそがポーリングです)、loadをエコーバックすると永遠に再取得してしまいます。data-hx-swap="outerHTML"— 領域はサーバが返す完全な<section>(同じ id、クラス、属性)で自分自身を置き換えます。 そのためどのスワップも冪等で、領域のトリガーの主導権はサーバに 残ります。data-hx-indicator="closest .hc-data-region"— 遅いポーリングでは コンテンツをちらつかせる代わりに、ヘッダーの.hc-spinner.htmx-indicatorが点灯します。
ポーリングされるエンドポイントは常に安価(高速な COUNT クエリや 非正規化された読み取りモデル)かつ冪等に設計してください。
内側コンテンツのフラグメント(innerHTML)
Section titled “内側コンテンツのフラグメント(innerHTML)”内側のコンテンツだけを返す(ラッパー要素のない)既存の フラグメントエンドポイントを後付け(レトロフィット)する場合は、 トリガーを素のコンテナに置いたまま、中身をスワップさせます:
<section id="orders-summary" data-hx-get="/orders/summary" data-hx-trigger="load, every 10s" data-hx-swap="innerHTML"> <p class="hc-field__message">Loading…</p></section>- コンテナ自体(トリガーごと)はそのまま残り、中身だけが置き換わる ため、再アームするものはなく、トリガーは書いたとおりに時を刻み 続けます。
- トレードオフ: フラグメントが属性を運ばないため、サーバは領域を
調整できなくなります — ポーリング間隔の変更、ポーリングの停止、
新しいイベントの待ち受けなど。サーバを制御できるなら上の
自己置換型
hc-data-region形式を選び、この形式は レトロフィットのために取っておいてください。
領域丸ごとの自己置換(hx-select)
Section titled “領域丸ごとの自己置換(hx-select)”エンドポイントがフルページしか返せない(フラグメント用ルートの ないサーバレンダリングビュー)場合は、領域にそのページから自身の 置き換え分を切り出させ、自分をスワップさせます:
<div id="page-content" data-hx-get="/ops/console/outbox" data-hx-trigger="every 15s" data-hx-select="#page-content" data-hx-target="this" data-hx-swap="outerHTML"> …</div>data-hx-select="#page-content"がレスポンスから一致する要素を抽出 し、data-hx-swap="outerHTML"がトリガーごと領域を置き換えます。- これは 1 ティックごとにフルページ描画のコストがかかります — サーバを
制御できるならフラグメントエンドポイント(自己置換型
hc-data-region形式、または上のinnerHTML形式)を選び、hx-selectは後付け(レトロフィット)のために取っておいて ください。 outerHTMLでは、領域のevery 15sタイマーはスワップのたびに 再スタートします。トリガーにloadがなければ初回描画はサーバ レンダリングされた HTML なので、フラッシュはありません。
イベント駆動の更新
Section titled “イベント駆動の更新”ポーリングを htmx カスタムイベントと組み合わせれば、サーバの アクションが領域に即時更新を求められます:
<section id="orders-summary" class="hc-data-region" data-hx-get="/orders/summary" data-hx-trigger="load, every 30s, orders:refresh from:body" data-hx-swap="outerHTML" data-hx-indicator="closest .hc-data-region" aria-busy="false"> …</section>サーバは任意の書き込みエンドポイントで HX-Trigger からイベントを
発火します:
HTTP/1.1 200 OKHX-Trigger: {"hc:toast":{"message":"Created"},"orders:refresh":true}from:body は document.body 上のイベントを待ち受けます —
HX-Trigger のイベントを htmx が発火する場所です。
reveal 駆動(初回読み込みの遅延)
Section titled “reveal 駆動(初回読み込みの遅延)”ファーストビューより下のデータ領域では、領域がスクロールで見えるまで 初回取得を先送りします:
<section id="invoices-summary" class="hc-data-region" data-hx-get="/invoices/summary" data-hx-trigger="intersect once, every 30s" data-hx-swap="outerHTML" data-hx-indicator="closest .hc-data-region" aria-busy="false"> <p class="hc-field__message">Loading when visible…</p></section>intersect onceは要素がビューポートに入ったとき、一度だけ発火。- 後続の
every 30sは初回取得の後にだけ時を刻み始めます。
サーバレスポンス契約
Section titled “サーバレスポンス契約”サーバは完全な <section> — 同じ id、同じクラス、同じ属性 —
を返し、スワップを冪等にします。意図的な違いは 1 つだけ:
再描画されるフラグメントはトリガーから load を落とします
(htmx はスワップされたばかりの要素すべてで load を発火するため、
エコーバックすると永遠に再取得してしまいます):
<!-- GET /orders/summary --><section id="orders-summary" class="hc-data-region" data-hx-get="/orders/summary" data-hx-trigger="every 10s" data-hx-swap="outerHTML" data-hx-indicator="closest .hc-data-region" aria-busy="false"> <header class="hc-data-region__header"> <h2>Orders</h2> <span class="hc-spinner htmx-indicator" aria-hidden="true"></span> </header> <dl class="hc-stack"> <div> <dt>Open orders</dt> <dd>14</dd> </div> <div> <dt>Awaiting payment</dt> <dd>3</dd> </div> </dl></section>フラグメントが領域の属性を運ぶため、主導権はサーバに残ります:
ポーリング間隔を変える、データが確定したらポーリングを止める、
行がなくなったら明示的な空状態を描画する、といったことができます。
innerHTML のレトロフィット形式
では、代わりに内側のコンテンツ(上の <dl>)だけを返します。
htmx(HX-Request: true)とフルページロードの両方に同じコンテンツを
返してください。キャッシュは標準の Cache-Control ヘッダーで調整
できます。htmx はそれを尊重します。
| リクエスト | レスポンス |
|---|---|
GET /orders/summary(ポーリング・イベント・reveal) | 200 + 完全な <section>(innerHTML レトロフィットでは内側コンテンツだけ) |
GET /orders/summary(HX-Request なし — フルページロード) | 200 + 同じコンテンツをフルページで包んだもの |
| 領域を更新させたい任意の書き込みエンドポイント | 2xx + HX-Trigger: {"orders:refresh":true} |
GET /orders/summary(失敗) | 非 2xx — スワップされず、領域は直前の描画を保ちます |
htmx ≥ 2 は非 2xx レスポンスをスワップしないため、ポーリングが失敗
しても直前の描画が見えたままです — 領域は「少し古い」に劣化するだけで、
壊れることはありません。ユーザーに知らせるには、エラーレスポンスに
HX-Trigger: {"hc:toast":{…}} ヘッダーを付けてください
(トーストレシピを参照)。
インジケーター
Section titled “インジケーター”ポーリングのエンドポイントが遅く、コンテンツのちらつきが問題になる
なら、htmx-indicator でスピナーを包みます。上の hc-data-region
形式は data-hx-indicator="closest .hc-data-region" ですでにこれを
配線済みです。innerHTML 形式では、インジケーターのセレクタが
スピナーに届くよう、スピナーを含む要素にリクエスト属性を置いて
ください(closest は祖先だけを遡り、find は子孫を探索します):
<section id="status" data-hx-get="/status" data-hx-trigger="load, every 5s" data-hx-target="#status-body" data-hx-swap="innerHTML" data-hx-indicator="find header .hc-spinner"> <header> <h2>Sync status</h2> <span class="hc-spinner htmx-indicator" aria-hidden="true"></span> </header> <div id="status-body">…</div></section>アクセシビリティ
Section titled “アクセシビリティ”- 変化を知らせることがユーザーの利益になる場合(カウンターなど)は
領域に
aria-live="polite"を、更新が騒がしく装飾的ならaria-live="off"を付けてください。 - 更新のたびにフォーカスを移動しないでください。フォーカスを 乗っ取るポーリングは、キーボードユーザーにもスクリーンリーダーにも 敵対的です。
- データセットが空になったら明示的な空状態を表示し、支援技術が静かに 空になったセクションに出くわさないようにしてください。
プログレッシブエンハンスメント
Section titled “プログレッシブエンハンスメント”- JavaScript なしでは、領域は初期のサーバサイドコンテンツを描画し、 更新されません。空白のままにせず、妥当なデフォルトをセクション内に 描画してください。
- htmx なしでは、セクションは素の HTML として振る舞います。
- バックグラウンドタブはタイマーを絞ります。 ブラウザは非アクティブ
なタブでポーリングを遅くします。リアルタイム精度を
every 5sに 頼らないでください。 - 同期ポイント。 秒未満の鮮度が重要なら、ポーリングを SSE や WebSocket と組み合わせます。データ領域のレシピは同じままで、 トリガーだけが変わります。
- コスト。 スケールでの毎秒ポーリングは高くつきます。バックプレーン
を持ったら、イベント駆動更新(
from:body)を選んでください。