- URLの構造は単なるアドレスを超えて、アプリケーションの状態を保存・復元する手段として機能する
- PrismJS のダウンロードページのように、1つの URL だけでテーマ・言語・プラグイン設定を完全に再現できる事例を提示
- パス、クエリパラメータ、フラグメントなど各構成要素が、階層的なナビゲーション・フィルタリング・クライアントナビゲーションなど多様な状態を表現する
- 検索フィルタ、ページネーション、表示モード、日付範囲などは URL に含めるのに適しており、機密情報や一時的な UI 状態は不適切
- よく設計された URL は共有性・予測可能性・キャッシュ効率を高め、Web アプリケーションの信頼性とユーザー体験を強化する
URLの可能性
- URL は単なるリソースのアドレスではなく、ユーザーインターフェース(UI) であり、状態コンテナとして機能する
- 共有、ブックマーク、ブラウザ履歴、ディープリンクなどで状態を自動的に保持する
- 1991年から Web の基本的な状態管理メカニズムとして機能してきた
- URL の各構成要素は異なる種類の状態を表現する
- パス(Path) : 階層的なリソース探索 (
/users/123/posts)
- クエリ(Query) : フィルタ・オプション・設定 (
?theme=dark&lang=en)
- フラグメント(Fragment) : ドキュメント内の位置や SPA ルーティング (
#features, #/dashboard)
- Text Fragments 機能により、ページ内の特定テキストへ直接リンクできる
クエリパラメータのパターン
- 区切り文字(delimiter) で複数の値を1つのキーに入れる方式 (
?tags=frontend,react,hooks)
- ネストしたデータを JSON または Base64 にシリアライズ (
?config=eyJyaWNrIjoicm9sbCJ9==)
- ブールフラグはキーの存在有無で表現 (
?mobile)
- 配列表記(bracket notation) は
tags[]=frontend&tags[]=react 形式で複数値を表現
- Node の
qs や Express ミドルウェアなどでは自動認識されるが、標準化はされていない
- 重要なのは一貫性を保つこと
URLによる状態表現の事例
- PrismJS: URL ハッシュにテーマ・言語・プラグイン設定全体を保存
- GitHub:
#L108-L136 で特定のコード行範囲を強調
- Google Maps: 座標・ズームレベル・地図タイプを URL に含める
- Figma: キャンバス位置・ズーム・選択要素など作業コンテキストを URL で共有
- ECサイト: フィルタ・並び替え・価格帯を URL に含めて検索状態を復元
フロントエンドエンジニアリングのパターン
- URL に含めるのに適した状態
- 検索語、フィルタ、ページ・並び替え、表示モード、日付範囲、アクティブタブ、UI 構成、機能フラグ
- URL に不向きな状態
- パスワード・トークンなどの機密情報、一時的な UI 状態、未保存入力、大容量データ、高頻度の状態
- 判断基準: 他のユーザーが同じ URL をクリックしたとき、同じ状態を見るべきか
JavaScript実装
URLSearchParams API でクエリパラメータの読み書きが可能
pushState で新しい履歴項目を追加し、replaceState で現在の項目を更新
popstate イベントでブラウザの戻る操作時に UI を復元
React実装
- React Router の
useSearchParams フックで URL 状態を簡潔に管理
- パラメータの読み取り・更新時に URL と UI を自動同期する
URL状態管理のベストプラクティス
- デフォルト値は URL に含めない (
?theme=dark だけを保持し、デフォルト値はコード側で処理)
- デバウンスで入力中の URL 過剰更新を防止 (
lodash.debounce を活用)
- pushState vs replaceState
pushState: フィルタ変更・ページ移動など、戻れるべき状態
replaceState: 検索入力など細かな修正
URLを契約(Contract)として見る
- よく設計された URL はアプリケーションとユーザーの間の明示的な契約として機能する
- 公開/非公開、クライアント/サーバー、共有/セッション状態の境界を明確にする
- 可読性の高い URLは意図を説明し、人にも機械にも理解しやすい
example.com/products/laptop?color=silver&sort=price のような形は意味伝達に有利
- キャッシュ効率の向上
- 同じ URL は同じリソースとみなされ、キャッシュヒット率が上がる
- クエリパラメータでキャッシュのバリエーションを制御できる
- バージョン管理と実験
?v=2, ?beta=true, ?experiment=new-ui などで API バージョン・A/B テストを区別
避けるべきアンチパターン
- SPA でメモリ内状態だけを保持して、リロード時に状態を失う
- 機密情報を URL に含める (
?password=secret123)
- 不明確なパラメータ名 (
?foo=true&bar=2 ではなく ?mobile=true&page=2)
- 複雑な JSON を Base64 でエンコードして過度に長い URL を生成
- URL 長の制限を超える(ブラウザ・サーバー・CDN に制約がある)
- 戻るボタンを無効化する (
replaceState の乱用で発生)
結論
- 良い URL はコンテンツを指し示す以上に、ユーザーとアプリケーションの間の対話を表現する
- URL は意図・コンテキスト・共有可能性を運ぶ、最も古く洗練された状態管理手段である
- Redux・MobX・Zustand・Recoil など複雑な状態管理ツールは存在するが、
URL という基本機能を忘れないことこそが、本当の Web の強みである
- リロード時に状態を失うアプリは、Web の本質的な特性を見落としている
2件のコメント
Hacker Newsのコメント
コードレビューの際、できるだけ多くの状態(state) をURLに保存するようにしている
リロード後にまったく別の場所へ移動したり、共有したURLが見当違いの画面を表示したりするのは、ユーザーにとって侮辱的ですらある
このやり方は開発速度を落とすが、チーム内でUXへの意識が高まり、ビューにどれだけ多くの状態を持たせているかを明確に把握できる
URLが一種の公開APIになって制約が生まれるという懸念もあるが、ほとんどのURLは短期的にしか使われないため、大きな問題ではないと思う
必要なら、読み込み時に古いURLを新しいURLへマイグレーションするコードで対処できる
パス(path)の代わりにクエリパラメータを使うほうが少し良いと思う
ユーザーの立場では、「戻る」という言葉はブラウザボタンと結び付いているので混乱する
リロードで状態が初期化されるほうがまだ腹立たしくない。「リロード = 最初からやり直し」という認識があるからだ
JSですべてを処理すると、こうした基本機能が微妙に壊れる
だが、これまで30人以上のUXデザイナーと仕事をしてきても、URLについてのガイドを受けたことはない
特にモバイルではページを初期状態に戻すのが難しく、リロードが最も手っ取り早い解決策になる
無限スクロールや複雑なフィルターUIでは、URLに状態が多いほど初期化がさらに面倒になる
すでにUXに不満がある状況でURLまで整理しなければならないなら、それはユーザーにとって二重のストレスだ
デジタルリテラシーの高い人でさえ、URLとDNSの理解度は低いと感じる
フィッシングの危険を減らし、URLパラメータ(
?t=_,utm_)の意味を理解し、共有前に個人情報を取り除けるようであるべきだHTTPSの鍵マークが「信頼」を意味しないことも知っておくべきだ
URLを状態コンテナとして使うと内部構造が露出し、バージョン管理が必要になる
ブラウザ間の互換性や認証フローでも問題が起こり得る
それでも、コマンドライン引数のようにできるだけ多くの状態をURLに露出させようとしている
ただしこれは意図的なトレードオフであって、無知や経験不足のせいではない
古いライブラリだが今でも有用な Rison を勧めたい
JSONをURLにきれいに保存でき、ElasticのKibanaでも使われている
例: http://example.com/service?query=q:'*',start:10,count:10
システムが進化すれば状態構造も変わるため、URLに状態を入れると進化が制約される
URLは基本的に永続的な文字列だからだ
その代わり、URLを一種のプロトコルと見なし、状態をエンコード・デコードする方式が適切だと思う
単純なページなら、状態全体をURLに載せることも可能だ
しかしフィードのように「リロード時に最新状態へ戻るべきか?」といったユーザーの期待値によって変わる
URL長の制限はブラウザ・サーバー・CDN・検索エンジンの設定によって異なるが、通常は2000文字以下だ
この制限内でどれだけ多くの状態を持たせられるか、あるいは別のアプローチが必要かを考えさせられる
- . _ ~)が使えるので、情報密度はかなり高いdraw.ioは状態全体をURLに保存して共有できる
ダイアグラムデータがBase64でエンコードされ、1つのリンクで完全に復元できる
ただし、これが「state container」の定義に当てはまるのかは確信がない
私はセルフホストアプリでhash routing (#/dashboard) を使っている
サーバー側のURLリライト(.htaccessなど)が不要なので、完璧ではないにせよデプロイ環境の制約を減らせる
最新のMicrosoft Teamsではすべての画面が1つのURLで処理されるため、ブックマーク不可になっている
特定のチームやチャンネルを直接開けず、とても不便だ
HATEOASは名前が良くないせいで注目されにくいが、結局はWebの基本概念だ
しかしサーバーとクライアントの両方を制御する環境では、追加の複雑さが生じるだけだ
特にクライアントが依然としてエンドポイント構造を知る必要があるなら、URLを不透明にするだけになってしまう
タブのスリープ機能をよく使うのですが、URLを固定してひとかたまりで動くWebアプリは、スリープに入ると情報が消えてしまいます。
しかも、そういうWebページはどれも重いので、スリープを無効にするわけにもいきません。