「保存した」はどこに残る?ワインQで学ぶlocalStorage
ワインQで保存した問題が、新しく開いたページにも残るのはなぜ?localStorageと会員サーバー保存の違い、同一オリジン、タブ間の通知、保存に失敗したときの挙動を実画面と図で整理します。
CONTENTSこの記事の目次7項目
検証用のワインQで「あとで見返す」を押し、同じURLを新しいタブで開いてみました。クイズは開始前の画面に戻っているのに、見返す問題は1件残っています。「画面を閉じても残るなら、会員データとして保存されている?」と思いそうですが、この保存先はブラウザー内のlocalStorageです。
ログインできるサイトでも、すべての機能がアカウントへ保存されるとは限りません。今回は「保存したデータはどこにあるのか」を、ワインQの動きと実装からたどります。
残しているのは、問題そのものではなく問題ID
ワインQがlocalStorageへ書き込むのは、見返したい問題のIDを並べた配列です。その配列をJSON文字列にして保存し、表示するときに読み戻します。問題文・選択肢・解説はアプリが持つ問題集から、IDに対応するものを探します。
選択した回答、得点、クイズの途中位置は別の画面状態で管理しています。それらをまとめて永続保存する処理はありません。見返す問題が残ることと、中断したクイズをそのまま再開できることは別です。問題集から削除されたIDに対応する問題も、復習一覧には表示されません。
localStorageと、会員のサーバー保存は何が違う?
localStorageは、そのブラウザーの保存領域です。通常はページの再読み込みやブラウザーの再起動をまたいで残り、保存時に自動で有効期限が付く仕組みではありません。ただし、利用者によるサイトデータの削除、ブラウザーの設定、プライベートブラウジングなどで失われたり、保存できなかったりします。永久保存の保証はありません。
会員のサーバー保存では、サービス側がアカウントに対応するデータを保存し、別の端末からも取得できる仕組みを実装します。ワインQの見返す問題には、その送受信処理がありません。サイトへログインしただけで、端末内の問題IDが会員データへ移動することはありません。
「同じサイト」は、ドメイン名だけでは決まらない
localStorageの区切りとなるオリジンは、基本的に「スキーム・ホスト・ポート」の組み合わせです。たとえば同じホストでもhttpとhttps、ポート番号が異なれば別のオリジンです。パスだけが違うページは、同じオリジンの保存領域を使えます。
同じオリジンでも、別のブラウザーや別プロファイル、別端末の保存領域とは自動でつながりません。今回は同じローカルアプリを127.0.0.1とlocalhostで開き、前者では保存した1件が見え、後者では0件になることを確認しました。これは別オリジンの実測であり、別端末でログインする試験を行ったわけではありません。
同じタブと別タブでは、変更の伝え方も違う
localStorageの値が変更されると、同じ保存領域を共有する別のページへstorageイベントが届きます。一方、書き込んだページ自身には、その変更によるstorageイベントは届きません。
ワインQはこの違いに合わせ、保存成功後に同じタブへ独自イベントも送っています。ReactのuseSyncExternalStoreが両方のイベントを購読し、保存値を読み直すことで、ボタンや件数を表示へ反映します。これは外部の保存値と画面をつなぐ仕組みで、クラウド同期を追加する機能ではありません。
- 今回の実画面確認:保存したタブのボタンが「保存済み」に変わる。
- あらかじめ開いておいた同じオリジンの別タブ:0件から1件へ変わる。
- 新しく読み込んだ同じオリジンのページ:復習一覧に同じ問題が残る。
- 異なるオリジンのページ:0件のまま。
保存できない場合、今の画面はどうなる?
実装はlocalStorageの読み書きをtry/catchで囲んでいます。読み込みに失敗したら空の配列として扱い、書き込みに失敗したら変更イベントを送らずに処理を終えます。保存エラーを知らせるメッセージや、会員サーバーへ保存先を切り替える処理はありません。
今回、実際の読み書き処理を取り出し、保存領域が例外を投げる模擬条件でも確認しました。読み込み失敗では空の値が返り、書き込み失敗は外へ例外を投げず、変更通知も発生しませんでした。ブラウザーの容量を実際に使い切った試験ではありません。
例外を捕まえることと、保存に成功することは違います。保存ボタンを押したら、保存済みの表示や復習一覧まで確認すると確実です。また、localStorageは同じオリジンで動くスクリプトから扱えるため、ブラウザー内にあるという理由だけで秘密情報の安全な保管場所とは考えないようにしましょう。
「保存しています」を、もう一段具体的にする
保存の説明では、「何を」「どこへ」「どの範囲で共有し」「いつ失われ得るか」を分けてみてください。ワインQなら「復習対象の問題IDを、そのブラウザーのlocalStorageへ保存しています。同じ保存領域のタブには反映しますが、会員アカウントや別端末とは同期しません」と説明できます。
READ MOREワインQの使い方と公開時の記録カテゴリ選択や、見返す問題の使い方はこちらで紹介しています。→参考資料・確認範囲
- MDN Web Docs「Window: localStorage property」「Window: storage event」「Storage: setItem() method」。保存範囲・通知・例外の一般仕様を確認。
- React公式「useSyncExternalStore」。外部ストアの購読とスナップショットの役割を確認。
- Appli ParkのWineQuiz.tsxとワインQ仕様書。2026年9月12日のローカル実装・実画面・読み書き例外の模擬検証を根拠にしています。