Browser storage 看起來簡單,直到它成為 authentication flow、multi-tab 體驗,或必須撐過 schema changes 的應用的一部分。
- API 很少是困難之處。真正的決策是 誰需要這些資料、它應該存活多久,以及它跨越了哪條安全邊界。
- 瀏覽器並沒有名為
cookieStorage的標準 API。傳統 cookies 走document.cookie;較新的非同步 Cookie Store API 走cookieStore。它們管理的是同一層底層 cookies,兩者的行為都不像 Web Storage。
概覽
| Property | localStorage | sessionStorage | Cookies |
|---|---|---|---|
| Typical lifetime | Until explicitly cleared or evicted | Current tab's page session | Session-based or until their expiry date |
| Sent to server | No | No | Yes, on matching requests |
| JavaScript access | Yes, synchronously | Yes, synchronously | Yes, unless marked HttpOnly |
| Best suited for | Persistent, non-sensitive preferences | Temporary state that should survive reloads | Server-managed sessions and small server-readable values |
- Web Storage 屬於 client。Cookies 參與 HTTP request lifecycle。
- 誰都不該被當成受信任的 database。
localStorage
localStorage 把資料為某個 origin 持久保存,撐過 page reloads 與 browser restarts。
ts
const preferences = {
theme: "dark",
density: "compact",
}
localStorage.setItem("preferences:v1", JSON.stringify(preferences))
const stored = localStorage.getItem("preferences:v1")
const parsed = stored ? JSON.parse(stored) : nullts
type Preferences = {
theme: "light" | "dark"
}
function readPreferences(): Preferences | null {
try {
const value = JSON.parse(localStorage.getItem("preferences:v1") ?? "null")
if (value?.theme === "light" || value?.theme === "dark") {
return value
}
} catch {
// Corrupt or manually edited storage should not break the application.
}
return null
}- 適合少量、非敏感 preferences:theme、已關閉的 notice、未完成的 local draft。
- Values 是 strings,訪問是 synchronous,每一次 read 或 write 都會擋住 main thread。Capacity 與 eviction 因 browser 而異。它是小型 persistence 機制,不是 database。
- Version keys,並在讀取時 validate:已部署的應用會變,舊的 browser 資料還在。
- Failure: 把 session tokens、passwords,或長期 bearer tokens 放進
localStorage。該 origin 上的任何 script 都能讀它,包括 XSS。偷走這個值,就足以在別處重用。
sessionStorage
sessionStorage 有類似的 string-based API,但它的 lifetime 綁在 page session。
ts
sessionStorage.setItem(
"checkout:draft",
JSON.stringify({ step: 2, deliveryMethod: "pickup" })
)- 它能撐過同一 tab 裡的 reloads,通常在該 tab 或 window 關閉時被清除。分開的 tabs 得到分開的 storage。
- 適合臨時 UI state:multi-step form、return URL,或應撐過一次意外 refresh、卻不該變成永久 preference 的 filters。
- Failure: 把
sessionStorage當成安全邊界。頁面上的 scripts 仍然能讀它,用戶也可以用複製或恢復 tabs 的方式,讓 lifetime 假設變得沒那麼明顯。它適合的是 product lifetime 與資料匹配的時候,而不是因為它比localStorage更安全。
Cookies 與 Cookie Store API
Cookies 不同,因為 browser 可以把它們附在 HTTP requests 上。
http
Set-Cookie: session=opaque-value; Path=/; HttpOnly; Secure; SameSite=Laxts
const preference = await cookieStore.get("theme")
await cookieStore.set({
name: "theme",
value: "dark",
path: "/",
sameSite: "lax",
})- 適合 server 需要這個值的時候,尤其是 server-managed sessions。Security-sensitive cookies 通常應由 server 創建。
HttpOnly阻止 JavaScript 讀取 cookie,Secure把它限制在 HTTPS,SameSite有助於限制 CSRF。它們降低風險;它們不能取代 output escaping、CSP、CSRF analysis,或 session expiration 與 rotation。- 傳統
document.cookie是 synchronous 的,解析也很彆扭。Cookie Store API 在支持的地方是 asynchronous。Client-side code 仍然無法透過cookieStore讀取HttpOnlycookie——這正是限制的意義。 - Failure: 因為
cookieStore看起來像localStorage,就把 preferences 塞進 cookies。Cookies 很小,domain 與 path 規則很微妙,而且隨 requests 帶上的 cookies 每次都會增加網絡開銷。
預設選擇
以最短的有用 lifetime,存放最少的資料。
- 當資料只需在當前頁面打開期間存活時,使用 in-memory state。
- 當臨時 state 應在單一 tab 中撐過 reloads 時,使用 sessionStorage。
- 對應需要跨多次訪問持久保存的少量、非敏感 preferences,使用 localStorage。
- 當 server 必須收到該值時使用 cookies,尤其是放在 secure、HTTP-only cookie 中的 opaque session identifier。
- 當應用需要大量資料、結構化 records、transactions,或 offline 行為時,改用 IndexedDB。
- Browser storage 由用戶控制,可被清除或修改,絕不應被當成權威的 source of truth。讀取時驗證、處理 storage failures,並讓 server-side authorization 獨立於 client 可編輯的任何內容。
選擇 storage 是架構決策,而不是 API 偏好。從資料的 lifetime 與信任等級出發;正確的 browser primitive 通常會隨之而來。
Recap Q&A
閱讀下一篇筆記
JavaScript 核心概念