URL 就是你的狀態(Your URL is your state)

核心結論:URL 不只是位址,它本身就是一份可分享、可還原的狀態容器。把「別人點開這個連結該不該看到相同畫面」的狀態放進 URL,就能免費得到瀏覽器前一頁/後一頁、書籤、分享等能力。相關筆記:Create Search ParamsRemix Loader Function

為什麼 URL 適合當狀態

URL 天生能記住很多東西,並且很好地被瀏覽器的上一頁/下一頁串起來。核心問題是:如果我需要「還原」某個網頁的狀態,該怎麼做?答案往往是把狀態編碼進 URL。

現代 URL 可以包含許多資訊,例如:

  • GitHub 用 fragment 自動高亮特定行數:.../XeePhotoshopLoader.m#L108-L136
  • Google Maps 把座標與縮放大小放進路徑:.../@22.443842,-74.220744,19z

哪些狀態該放進 URL

在討論實作前,要先釐清「並非所有狀態都該放進 URL」。

適合放進 URL 的狀態:

  • 搜尋查詢與篩選器
  • 分頁與排序
  • 檢視模式(列表/網格、深色/淺色)
  • 日期範圍與時間段
  • 已選項目或當前啟用的分頁
  • 影響內容呈現的 UI 配置
  • 功能開關與 A/B 測試版本

不適合放進 URL 的狀態:

  • 敏感資訊(密碼、token、個人識別資訊)
  • 臨時 UI 狀態(modal 開關、下拉選單展開)
  • 進行中的表單輸入(尚未儲存的變更)
  • 極其龐大或複雜的巢狀資料
  • 高頻瞬態(滑鼠位置、滾輪位置)

判斷準則: 如果其他人點擊這個 URL,他們是否應該看到相同的狀態?該 → 放進 URL;不該 → 用其他狀態管理方式。

最佳實踐

1. 不要把預設值塞進 URL

// Bad: URL 被預設值塞滿
?theme=light&lang=en&page=1&sort=date
 
// Good: 只放非預設值
?theme=dark  // light 是預設,所以省略

預設值應在程式中處理:

function getTheme(params) {
  return params.get('theme') || 'light'; // 預設值在 code 裡處理
}

2. pushState 還是 replaceState

  • pushState:希望產生新的歷史紀錄。用於更改搜尋條件、切換到新分頁等,讓使用者能快速返回上一個狀態。
  • replaceState:不希望每次都新增歷史紀錄。例如搜尋框輸入的即時更新。

Anti-pattern

  • 把敏感資訊(如 password)放進 URL
  • 過長的 URL
  • replaceState 破壞了原先的瀏覽器返回行為:
// 錯誤地取代狀態
history.replaceState({}, '', newUrl); // 這裡其實該用 pushState

我的回顧

回到最一開始的問題:要不要「還原」狀態,取決於這個狀態是否需要分享出去、是否值得保留。例如表單輸入到一半的狀態,其實就不適合保留在 URL 中。