コンテンツにスキップ

SSE ライブ更新

sse-updatesサーバプッシュ UI のための公認パターンです: ユーザーが操作したからではなく、サーバがそう言ったから再描画される フィード、ステータスパネル、データグリッド。 htmx SSE 拡張EventSource を所有し、マークアップがストリームとイベント名を宣言し、 サーバはデータが完成済みフラグメントである名前付きイベントを送り ます — 他のすべてのレシピと同じ「マークアップこそワイヤ契約」の立場 です。マークアップのバージョニングポリシーの 下で安定しています。

フラグメントのプッシュに Hypermedia Components のビヘイビアは不要 です — サーバプッシュの通知とドメインイベントには SSE トーストレシピを 参照してください。

別名: リアルタイム更新、プッシュ更新。

ストリームは約 25 秒のスクリプト済みシーケンスを一度だけ再生し、 stream:done で意図的に自ら終了します: タイムスタンプつきの アクティビティ項目がフィードの先頭に追加され、ステータスパネルが 再描画され、1 つのプッシュが同じイベントの中でアラートバッジも out-of-band で更新し、さらに 2 回の products:rows プッシュが データグリッドの tbody に行 1 ページ分をスワップします (下記の合成)。シーケンスはページ 読み込み時に始まります — 終わってしまっていたら Replay the stream が新しいスコープ(新しい EventSource)を スワップして再実行します。

A scripted ~23 s sequence — activity items, status pushes, an OOB badge, grid rows.

Waiting for updates…

Alerts:0

    IDStatusUpdated
    Waiting for pushed rows…
    <div data-hx-ext="sse" data-sse-connect="/events"
    data-sse-close="stream:done">
    <ul id="activity" data-sse-swap="activity:item"
    data-hx-swap="afterbegin">
    <li class="hc-item">…server-rendered current items…</li>
    </ul>
    <section id="status" data-sse-swap="status:panel">
    …server-rendered current status…
    </section>
    </div>

    ストリームごとに 1 つの data-sse-connect スコープ。内側の各要素は 受け取るイベントを data-sse-swap で名指しし、イベントのデータは その要素の data-hx-swap を尊重してスワップされます(デフォルトは innerHTML、フィードには afterbegin)。拡張は data- プレフィックス 形の属性をサポートし、キットの data-hx-* 規約と揃います。

    retry: 5000
    event: activity:item
    data: <li class="hc-item">Deploy #42 started</li>
    event: status:panel
    data: <p>All systems normal</p><span id="alert-badge" data-hx-swap-oob="true">3</span>

    イベントはドメインで命名します(activity:itemstatus:panel)— 名前はワイヤ契約です。メッセージのデータは、同じプッシュの中で id に よって他のターゲットを更新する data-hx-swap-oob フラグメントも運べます (2 つ目の例はパネルとバッジの両方を更新します)。 datagrid-pager や 一括操作のレスポンスとまったく同じです。

    GET /eventsContent-Type: text/event-stream で応答します。各更新は 名前付きイベントで、その data: は 1 行に収めた完成済みの サーバレンダリングフラグメントです。HX-Request での分岐は ありません — EventSource のリクエストは htmx のヘッダーを運ばない ため、ストリームがこのエンドポイントの唯一の形です。

    リクエストレスポンス
    GET /events200 + Content-Type: text/event-stream — 名前付きイベント(event: activity:item)、data: 行ごとに完成済みフラグメント 1 つ
    切断後の再接続同じストリーム — EventSource がネイティブにリトライします(サーバの retry: ヒントを尊重)。現在の状態を再送するか、欠落を許容してください
    意図的な終了data-sse-close が名指しするイベント(ここでは stream:done)を送ります — 閉じられたストリームは閉じたままで、終了イベント自身のペイロードは決してスワップされません
    クロスオリジンのストリーム拡張は withCredentials: true で接続するため、サーバには Access-Control-Allow-Credentials が必要です

    .hc-datagrid__body の tbody に data-sse-swap + data-hx-swap="innerHTML" を置けば、行 1 ページ分をプッシュできます。 ページャーの keep-the-tbody ルールがそのまま適用されます: グリッドのオブザーバーはスワップを生き延び、roles と sticky オフセットを再適用し、選択を導出し直します — そのため 選択アクションバーは、 プッシュされた行が届くと追加の配線なしで自分をクリアします。

    再接続は htmx ネイティブです: EventSource は自動でリトライし (サーバの retry: ヒントを尊重)、拡張は連続失敗にバックオフを追加 します — したがってサーバは再接続を予期して現在の状態を再送するか、 欠落を許容しなければなりません。ストリームは data-sse-close="<event>" で意図的に終了します(落ちたストリームは 再接続し、閉じられたストリームは閉じたままです)。デバッグ用に htmx:sseOpen / htmx:sseError / htmx:sseClose がスコープ上で発火 します。拡張は withCredentials: true で接続します — クッキーが同乗 します。

    初期 HTML に完全な現在の状態を描画してください。ストリームはそれを 新鮮にするだけです。JS なしでは、ページは単に最後のフル描画の時点の 鮮度になります — ローディングシェルなし、何も壊れません。

    ここでの主張は実 EventSource のブラウザテスト (test-browser/sse.spec.mjs)で固定されています。

    • プッシュされたフラグメントはフォーカスを動かさずにコンテンツを 置き換えます — ユーザーが現在フォーカスしている要素をライブ リージョンで包まないでください。
    • 読み上げはオプトインです: フラグメントが着地する領域に aria-live="polite" を置いてください。ステータス行はたいてい 読み上げるべきで、フィードは項目を逐一読み上げるべきでは ありません。
    • サーバプッシュの通知には SSE トーストレシピを 使ってください — トーストは正しい role="status" / role="alert" を 最初から備えています。
    • プッシュされたデータグリッドの行はグリッドの通常の再初期化を通る ため、roles とキーボードモデルはスワップのたびに生き残ります。