世界を動かす技術を、日本語で。

HTMXにおけるURL駆動型ステート

2025年7月30日原文(lorenstew.art)

概要

  • ReactからHTMXへの移行では、複雑な状態管理が不要になる利点
  • フィルタ・ソート・ページネーション・検索状態はURLで一元管理
  • URLパラメータを唯一の真実の情報源として活用
  • HTMXのフォーム連携と自動URL同期で追加ライブラリ不要
  • シンプルかつ強力なサーバーサイド主導アーキテクチャの実現

ReactからHTMXへ:状態管理の転換

  • React では 複雑なクライアントサイド状態管理 が必要
  • HTMX 移行後は、 状態管理の多くがサーバーサイド に移行
  • フィルタ、ソート、ページネーション、検索などの 状態はURLパラメータ に集約
  • URL が唯一の真実の情報源となり、 ブックマークや共有 が容易
  • 追加の状態管理ライブラリや複雑なJavaScriptの削減

URLを状態ストアとして利用するパターン

  • 例:/?status=active&sortField=price&sortDir=desc&page=2 というURLで 全状態を表現
    • 表示中のフィルタ、ソート順、ページ番号が明示的に記録
    • URL自体が状態の完全なスナップショット
  • このアプローチにより ブックマーク・共有・リロード時の状態保持 が可能

実装パターン:3つの基本ステップ

  • サーバーが URLパラメータを読み取り、状態に応じたビューをレンダリング
  • フォームhx-include による状態の保持とHTMXリクエスト連携
  • hx-push-urlブラウザURLの自動更新 を実現

ステップ1:サーバーでURL状態を読む

  • サーバーエンドポイントで クエリパラメータ を受け取り、初期表示を制御
    • 例:sortField, sortDir, status, page などをパース
    • デフォルト値を設定し、 データ取得クエリ に反映
  • テンプレート(例:ETA)で 状態をDOMに埋め込む
    • 各フィールド値をhidden inputやselected属性で表現

ステップ2:HTMLフォームで状態を維持

  • フィルタフォーム が全ての状態を保持
    • 例:<form hx-get="/api/data" hx-push-url="true" hx-params="*">
  • hidden inputで ソート条件・ページ番号 を維持
  • ソート可能なカラム はテンプレートロジックで動的にclassや値を切替
    • sortField, sortDir を動的に切替
    • 状態反映用のCSSクラス(例:sorted)を付与
  • JavaScript不要 で状態連携をHTMLのみで実現

ステップ3:hx-push-urlによるURL自動同期

  • hx-push-url="true"hx-params="*"
    • フォームデータが自動的にURLパラメータ化
    • 履歴管理(戻る・進む)も自動化
  • 状態管理やURL更新のための追加JavaScriptは不要

本パターンの利点

  • 状態がURLに明示的 なのでテスト容易
  • ブックマークや共有 で同じ状態を再現可能
  • SEO対応 (検索エンジンが全状態をクロール可能)
  • デバッグ容易 (現状態が常にアドレスバーで可視化)
  • ブラウザの戻る・進むボタン が期待通り機能
  • クライアント側の状態管理ライブラリ不要

運用上の注意点

  • URL長制限 (約2000文字):複雑なフィルタ時は省略名やサーバー側保存も検討
  • パラメータ検証 :サーバーで必ずバリデーション・サニタイズ
  • テスト容易性 :状態が明示的なため、モック不要でテスト可能

まとめ:URL主導アーキテクチャの価値

  • URLを状態ストアとすることで、Web本来の仕組みを最大限活用
  • シンプルな構成で 複雑な多段フィルタやソート にも対応可能
  • HTMXの利点 を最大限に活かす設計
  • 状態管理ライブラリ導入前に、まず URLベースの管理 を検討推奨
  • 多くの場合、 URLだけで十分かつ優れた体験 を実現

Hackerたちの意見

これは90年代のウェブアプリケーションのクラシックなパターンだね。HTMXなしでも驚くほどうまく機能するよ。

SPAの正当な不満の一つは、このパターンが分かりにくくなったことだよね。

次は、セレクタを使ってJSをマークアップの外に出すことかな。

ただ、ここにある例のURLは、ページ2の内容が新しいアイテムが追加されるたびに変わるから、(便利に)ブックマークできないんだ。真にブックマーク可能なリストのURLを得るには、「アイテムXから始まるページ」というアプローチが一番いいと思う。Xはそのアイテムの実質的にユニークなID(例えば、プライマリキーやIDを露出しないためのタイムスタンプ)だね。

なんでダウンボートされたのか分からないけど、君の言ってることは完全に正しいよ。君が言及した方法は、トークン/カーソル/キーセットベースのページネーションって呼ばれてる。

そうだね、このエッジケースを適切に解決するのはかなりの複雑さを加えることがある(君の解決策も同じ問題があるよね? 削除や更新が混乱を招くし、技術的に)。長期間の「冪等性トークン」を使ってイベントログを指す人も見たけど、ちょっとクレイジーだよね。解決しないことを考えるのも十分に価値があるかもしれないし、むしろ直感的なUXになるかもしれない(例えば、リーダーボードのために)。

Datomicと、データベースのバージョンをURLに入れようぜ :)

でも、ここにある例のURLは、ページ2の内容が新しいアイテムが追加されるたびに変わるから、(便利に)ブックマークできないんだ。リフレッシュのたびに内容が変わるのが「(便利に)ブックマークできない」理由は何?HNのトップページ(つまり「ページ1」)はそうだけど、あれはすごく便利なブックマークだよね。

パフォーマンスもかなり良くなってるしね。

次は、URLに特定の見出しに自動スクロールするための情報が含まれることがあるって言うんだろうね。

魔法だね!

それだけじゃなくて、最近はどんな部分にも自動スクロールできるよ、「URLテキストフラグメント」を見てみて。https://www.lexo.ch/blog/2025/01/highlight-text-on-page-and-... とにかく、投稿で提案されたことは特に難しいことではないけど、誰かにとってはすべてが新しいことかもしれないね。

Hacker Newsで議論の続きを見る