localStorageが保存されない:プライベートモードと容量の壁
遊びをつくる執筆にClaudeを使用しています。

ブラウザゲームを作って動作確認したとき、「スコアが保存されない」「ステージ進行がリセットされる」という現象に出くわすことがあります。コードを見直しても構文エラーはなく、localStorage.setItem() は呼ばれているはずなのに、再読み込みするとデータが消えている。この記事では、その原因として頻度が高い「プライベートブラウジング」と「容量超過」の二つに絞り、判断の仕方と対処の手順を説明します。 原因が一つとは限らないため、順番に切り分けることが大切です。まずプライベートモードかどうかを確認し、次に容量の問題を調べる、という流れで進めてください。
プライベートモードでは保存が「ゼロか一時か」になる
Chromeのシークレットウィンドウ、FirefoxやSafariのプライベートウィンドウでは、localStorage の扱いがブラウザによって異なります。共通して言えるのは、「ウィンドウを閉じると消える」か「そもそも書き込みが例外を投げる」かのどちらかだということです。
具体的な挙動はブラウザのバージョンによって変わるため断定はできませんが、開発者が押さえておくべき考え方は次の通りです。
- セッションをまたいだ保存を前提とするデータ(スコア、進捗)はプライベートモードで動作しないことがある
- 例外が投げられるケースでは、try/catch がなければスクリプト全体が止まる可能性がある
これを踏まえると、ゲームの保存処理は最低限 try/catch で囲んでおく必要があります。
try {
localStorage.setItem('score', JSON.stringify(score));
} catch (e) {
console.warn('保存できませんでした:', e);
}このように書いておくと、プライベートモードで例外が出てもゲームが止まらず、ユーザーには「保存できませんでした」と案内することができます。
自分の環境がプライベートモードかを確認する方法
デバッグ中に「なぜか保存されない」と感じたら、まずブラウザのアドレスバー左端のアイコンやウィンドウの色を確認してください。シークレット・プライベートウィンドウには視覚的な区別があります。または、ブラウザの開発者ツール(F12)→「Application」(Chromeの場合)→「Local Storage」を開いて、保存したキーが実際に書き込まれているかを直接確認するのが確実です。書き込みが失敗していれば、そこにキーは表示されません。
容量超過は例外で知らせてくれる
localStorage には上限容量があり、一般的にはオリジン(同じドメイン)ごとに数MBとされています。ゲームの進行データや設定程度であれば通常は問題になりませんが、画像データをBase64に変換して保存しようとしたり、ログを大量に積み重ねたりすると上限に達することがあります。
容量を超えた書き込みを試みると、ブラウザは QuotaExceededError(または NS_ERROR_DOM_QUOTA_REACHED)という例外を投げます。この例外は try/catch で捕捉できます。
try {
localStorage.setItem('mapData', largeString);
} catch (e) {
if (e.name === 'QuotaExceededError' || e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
console.warn('容量超過:古いデータを削除してください');
}
}容量超過が疑われるときは、開発者ツールのコンソールで次のコードを実行すると現在の使用量をざっくり確認できます。
let total = 0;
for (let key in localStorage) {
if (localStorage.hasOwnProperty(key)) {
total += (localStorage[key].length + key.length) * 2;
}
}
console.log('使用量(概算):', (total / 1024).toFixed(1), 'KB');これはあくまで文字数から算出する概算ですが、数百KBを超えているようなら設計を見直す目安になります。
容量を抑えるための判断基準
保存するデータが大きくなる場合は、以下の観点で削れるものを削ってください。
- 画像やバイナリをlocalStorageに入れない 画像は<img>タグやCSSで管理し、localStorageにはファイルパスや識別子だけを保存します。
- ログや履歴は件数を上限で管理する 「直近50件だけ保持する」などの上限を設けてシフトします。
- JSONはキーを短くする {"playerMaxScore": 9900} より {"pms": 9900} の方が蓄積すると差が出ます。ただし可読性とのバランスを考えて判断してください。
- 不要なキーをこまめに削除する 機能を作り直した際に古いキーが残りがちです。localStorage.removeItem() や開発者ツールからの手動削除で整理しておきます。
二つ以外に疑うべき点:オリジンの不一致
プライベートモードでも容量超過でもなく保存されない場合、file:// プロトコルで開いていることが原因のケースがあります。HTMLファイルをローカルで直接ダブルクリックして開くと、アドレスバーに file:///Users/... と表示されます。この状態では、ブラウザによって localStorage が完全に使えないか、起動のたびに分離されるかのどちらかになることがあります。
動作確認はローカルサーバーを立てて http://localhost:... 経由で行うか、開発用のサービスにデプロイして確認するのが確実です。VSCodeを使っているなら「Live Server」拡張が手軽です。
保存の失敗をユーザーに伝える設計
エラーを握りつぶさず、ユーザーに状況を伝えることも重要です。「オートセーブできませんでした」という一言をゲーム画面に表示するだけで、プレイヤーは「ブラウザの設定の問題かもしれない」と自分で気づくことができます。何も表示しないと、バグだと思ってゲームを閉じてしまいます。
実装の例としては、保存処理の catch ブロックでゲーム内のUIに通知を出す関数を呼ぶ形が簡潔です。通知の表示は数秒で消えるトースト形式が多く使われています。
保存が成功したかどうかを確認したい場合は、書き込んだ直後に localStorage.getItem() で読み返し、期待する値と一致するかをチェックする方法があります。容量超過が起きた場合は setItem が例外を出すので、getItem まで到達しないことで区別できます。
次の行動
まずブラウザの開発者ツール(Application → Local Storage)を開き、書き込みが実際に行われているかを目で確認してください。書き込まれていなければ、保存処理全体を try/catch で囲み、例外の name をコンソールに出してから原因を特定します。ゲームが完成してアソバースに投稿する際も、プレイヤーがどのブラウザのどの状態で開くか分からないため、保存失敗時の案内は入れておくことをおすすめします。

