「保存したはずのデータが消えている」「localStorage is not definedというエラーで処理が止まってしまった」——JavaScriptやReact、Next.jsで開発をしていると、こうしたlocalStorageのトラブルに一度は突き当たります。原因はコードの書き方だけでなく、実行環境やブラウザの仕様、本番特有のオリジンの違いなど多岐にわたるため、闇雲に修正しても解決しないことも少なくありません。
本記事では、SecurityErrorやQuotaExceededErrorといった具体的なエラーから、Next.jsのSSRでwindowが使えない問題、本番環境だけでデータが消える現象まで、原因と対処法を体系的に整理しました。エラーメッセージから逆引きできる構成になっているので、今まさにエラーに悩んでいる方はもちろん、今後同じ問題を再発させたくない方にも役立つ内容です。
localStorageにアクセスできない主な原因
localStorageにアクセスできない原因は、大きく分けて「実行環境がブラウザではない」「オリジン(URLの起源となるプロトコル・ドメイン・ポート番号の組み合わせ)がセキュリティ条件を満たしていない」「保存容量の上限を超えている」の3パターンです。エラーメッセージの種類によって原因が明確に切り分けられるため、まずはどのエラーが出ているかを確認しましょう。

localStorage is not definedが発生する実行環境
このエラーは、windowオブジェクトが存在しない環境でlocalStorageを呼び出したときに発生します。代表的なのはNode.jsのサーバーサイド実行環境や、Next.jsのサーバーサイドレンダリング(SSR)処理中です。
localStorageはブラウザが提供するWeb APIであり、Node.js単体には存在しません。
// Node.js環境で実行するとエラーになる
localStorage.setItem('key', 'value');
// ReferenceError: localStorage is not defined
つまずきやすいポイント:Next.jsやNuxt.jsのようなSSR対応フレームワークでは、コンポーネントの初回描画がサーバー側で行われるため、うっかりトップレベルでlocalStorageを呼び出すとビルドやレンダリングが失敗します。
SecurityErrorが発生するオリジン・プロトコルの条件
SecurityErrorは、ブラウザのセキュリティポリシーによってlocalStorageへのアクセス自体が拒否されたときに発生します。主な原因は、file://プロトコルでの直接実行や、Cookieやストレージをブロックする設定が有効になっている場合です。
try {
localStorage.setItem('test', '1');
} catch (e) {
if (e.name === 'SecurityError') {
console.error('ストレージへのアクセスが拒否されました');
}
}
よくある間違い:ローカルのindex.htmlをfile://で直接開いて動作確認し、「本番と同じはずなのに動かない」と誤解してしまうケースです。必ずローカルサーバー(http://localhostなど)経由で確認しましょう。
QuotaExceededErrorが発生する容量制限
QuotaExceededErrorは、localStorageの保存容量の上限(多くのブラウザでオリジンごとに5MB前後)を超えたときに発生します。大きな画像データやJSON化した大量のオブジェクトを保存しようとすると発生しやすいエラーです。
try {
localStorage.setItem('bigData', hugeString);
} catch (e) {
if (e.name === 'QuotaExceededError') {
console.error('保存容量の上限に達しました');
}
}
つまずきやすいポイント:容量上限はブラウザやモード(通常/プライベート)によって異なるため、「昨日は保存できたのに今日はエラーになる」という現象が起こり得ます。上限ぎりぎりの設計は避けましょう。
file://・iframe・クロスオリジンでブロックされるケース
localStorageはオリジン単位で分離されており、file://プロトコル、サードパーティのiframe内、異なるドメイン間では正しくアクセスできない、または完全に分離されたストレージ領域として扱われます。
| 実行条件 | localStorageの挙動 |
|---|---|
http://またはhttps://の通常ページ | 正常に読み書き可能 |
file://で直接開いたHTML | ブラウザによりアクセス不可またはエラー |
| 同一オリジンのiframe | 親ページと同じデータを共有 |
| クロスオリジンのiframe | 別オリジンとして完全に分離されたストレージ |
よくある間違い:親ページとiframe内で「同じデータが見える」と思い込んでしまうことです。オリジンが異なれば、localStorageのデータは共有されません。
◆◇◆ 【衝撃価格】VPS512MBプラン!1時間1.3円【ConoHa】 ◆◇◆JavaScriptでlocalStorageが動かないときの確認方法
localStorageの不具合を切り分けるには、まず基本メソッドが正しく動いているかをコンソールで確認し、次にDevToolsで保存先オリジンとデータの中身を目視確認するのが最短ルートです。エラーが出る場合はtry...catchで例外の種類を判定すれば、原因をピンポイントで特定できます。

localStorage.getItem()・setItem()の基本的な使い方
setItem(key, value)でデータを保存し、getItem(key)で取得します。値は必ず文字列として保存されるため、オブジェクトや配列を扱う場合はJSON.stringify()とJSON.parse()を組み合わせます。
// 保存
localStorage.setItem('username', 'taro');
// 取得
const name = localStorage.getItem('username');
console.log(name); // "taro"
// オブジェクトを保存する場合
const user = { id: 1, name: 'taro' };
localStorage.setItem('user', JSON.stringify(user));
// 取得して復元(※不正なデータによるエラーを防ぐためtry...catchを使用)
try {
const storedUser = localStorage.getItem('user');
if (storedUser) {
const restoredUser = JSON.parse(storedUser);
console.log(restoredUser);
}
} catch (e) {
console.error('データの復元に失敗しました。データが破損している可能性があります:', e);
}
つまずきやすいポイント:存在しないキーをgetItem()するとundefinedではなくnullが返ります[cite: 1]。また、取得した値をJSON.parse()する際、ストレージ内のデータが破損しているとSyntaxErrorが発生するため、オブジェクトを復元する際は必ずtry…catchでエラーハンドリングを行いましょう。
Chrome DevToolsで保存データとオリジンを確認する方法
DevToolsの「Application」タブ(Firefoxでは「ストレージ」タブ)を開き、左側の「Local Storage」からオリジンを選択すると、保存されているキーと値の一覧を確認できます。オリジンが想定と違う場合、データが見つからない原因の多くはここで判明します。
- DevToolsを開く(
F12または右クリック→検証) - 「Application」タブ→左メニューの「Local Storage」を展開
- 対象のオリジン(
https://example.comなど)をクリック - 一覧に表示されるキーと値を確認する
よくある間違い:http://とhttps://、またはwww.の有無だけでも別オリジンとして扱われるため、DevTools上で「データがない」と表示された際は、まずアドレスバーのURLとオリジンが一致しているかを確認しましょう。
try...catchでSecurityErrorとQuotaExceededErrorを判定する方法
localStorageの操作はtry...catch構文で囲み、error.nameプロパティを見ることでSecurityErrorとQuotaExceededErrorを区別できます。これにより、エラーの種類に応じたユーザー向けメッセージやフォールバック処理を実装できます。
function safeSetItem(key, value) {
try {
localStorage.setItem(key, value);
return true;
} catch (e) {
if (e.name === 'QuotaExceededError') {
console.warn('容量オーバーです。不要なデータを削除してください。');
} else if (e.name === 'SecurityError') {
console.warn('このブラウザ設定ではlocalStorageを利用できません。');
} else {
console.error('予期しないエラー:', e);
}
return false;
}
}
つまずきやすいポイント:e.nameではなくe.messageの文字列で判定しようとすると、ブラウザによってメッセージ文言が異なるため誤判定の原因になります。必ずnameプロパティで判定しましょう。
localStorage.clear()・removeItem()でデータを削除・初期化する方法
特定のキーだけを削除したい場合はremoveItem(key)、保存データを全て消したい場合はclear()を使います。バグ調査時にストレージを初期状態に戻したいときにもよく使われます。
// 特定のキーを削除
localStorage.removeItem('username');
// 全データを削除
localStorage.clear();
よくある間違い:clear()は同一オリジン内のすべてのキーを削除します。他の機能が使っているデータまで巻き込んで消してしまうことがあるため、本番コードでの安易な使用は避けましょう。
コストパフォーマンスに優れた高性能なレンタルサーバー
【Hostinger】Node.js・React・Next.jsなどでlocalStorageが使えない理由
Node.js・React・Next.jsでlocalStorageが使えない最大の理由は、これらの環境の一部がブラウザ外(サーバーサイド)で実行されるためです。windowオブジェクトが存在しないタイミングでコードが評価されると、localStorage is not definedエラーが発生します。

Next.jsのSSR・React Server Componentsでwindowが使えない原因
Next.jsのSSR(サーバーサイドレンダリング)やReact Server Componentsは、Node.jsサーバー上でHTMLを生成する仕組みです。このプロセスにはブラウザが提供するwindowやlocalStorageが存在しないため、コンポーネントのトップレベルやレンダリング中にlocalStorageへアクセスするコードを書くとエラーになります。
// NGな例:コンポーネントのトップレベルで実行される
function Profile() {
const name = localStorage.getItem('username'); // SSR時にエラー
return <p>{name}</p>;
}
つまずきやすいポイント:ローカル開発では気づかず、Vercelなどの本番環境でビルド・SSRが走った瞬間にエラーが表面化するケースが多く見られます。
typeof window !== 'undefined'とuseEffect()でクライアント側だけ実行する方法
typeof window !== 'undefined'という条件式で実行環境を判定し、ブラウザでのみコードを実行するようにガードします。Reactでは、コンポーネントの描画完了後(クライアント側)に一度だけ実行されるuseEffect()内にlocalStorage処理を書くのが基本パターンです。
import { useEffect, useState } from 'react';
function Profile() {
const [name, setName] = useState('');
useEffect(() => {
if (typeof window !== 'undefined') {
const stored = localStorage.getItem('username');
if (stored) setName(stored);
}
}, []);
return <p>{name}</p>;
}
useEffect()はブラウザ上でのレンダリング後にのみ実行されるため、この中に書いたコードはSSRの影響を受けません。
よくある間違い:typeof window !== 'undefined'のチェックだけをuseEffect()の外(コンポーネント本体の直下)に書いてしまい、結局SSR時にも評価されてエラーになるケースです。ガードとuseEffectはセットで使いましょう。
Node.js・React・Vue.js・AstroでlocalStorageを安全に扱う方法
フレームワークごとに「クライアント側限定で実行する」ための仕組みが用意されています。それぞれの作法に沿ってlocalStorageアクセスを実装することで、SSRエラーを防げます。
| 環境 | localStorageが使えないタイミング | 安全に使う方法 |
|---|---|---|
| Node.js(単体) | 常に利用不可 | ブラウザ向けコードと分離する |
| React | SSRやコンポーネント初回評価時 | useEffect()内で実行 |
| Next.js | SSR・RSC(Reactサーバーコンポーネント)実行時 | 'use client'ディレクティブを指定した上で、useEffect()内で実行(※'use client'単体では初回SSR時にエラーになります) |
| Vue.js | SSR(Nuxt.jsなど)のサーバー処理時 | onMounted()ライフサイクル内で実行 |
| Astro | サーバーサイドレンダリング時 | <script>タグやクライアントディレクティブ内で実行 |
つまずきやすいポイント:フレームワークが違えば「クライアント側でのみ実行する」ためのAPI名も異なります(React:useEffect、Vue:onMounted)。移植時に名前だけ変えて構造を変えないよう注意しましょう。
本番環境やブラウザでデータが消える原因
本番環境でlocalStorageのデータが消える主な原因は、「オリジンの変更」「ブラウザ独自の保存制限」「プライベートブラウジングモード」「アプリ内ブラウザやPWAの特殊な挙動」の4つです。ローカル開発では再現しにくいため、本番特有の落とし穴として押さえておく必要があります。

HTTP・HTTPS・ドメイン変更で保存先オリジンが分かれる仕組み
localStorageはプロトコル・ドメイン・ポート番号の組み合わせ(オリジン)ごとに独立して保存されます。開発時はhttp://、本番はhttps://というように、プロトコルが変わるだけでも別オリジン扱いとなり、以前保存したデータにはアクセスできなくなります。
| 変更前 | 変更後 | 同一オリジンか |
|---|---|---|
http://example.com | https://example.com | 別オリジン(プロトコルが異なる) |
https://example.com | https://www.example.com | 別オリジン(サブドメインが異なる) |
https://example.com:3000 | https://example.com:8080 | 別オリジン(ポートが異なる) |
https://example.com/page1 | https://example.com/page2 | 同一オリジン(パスは無関係) |
つまずきやすいポイント:独自ドメインへの移行やHTTPS化のタイミングで「ユーザーのログイン情報が消えた」という問い合わせが増えるのは、この仕組みが原因であることがほとんどです。
Chrome・Firefox・Edge・Safariの保存制限と挙動の違い
主要ブラウザは基本的にlocalStorageの仕様(Web Storage API)に準拠していますが、保存容量の目安や無操作時の削除ポリシーには差があります。
| ブラウザ | 保存容量の目安 | 長期間未使用時の挙動 |
|---|---|---|
| Chrome | オリジンごと約5MB前後 | 基本的に自動削除はされにくい |
| Firefox | オリジンごと約5MB前後 | 基本的に自動削除はされにくい |
| Edge(Chromium版) | Chromeとほぼ同等 | Chromeとほぼ同等 |
| Safari | オリジンごと約5MB前後 | ITPにより一定期間で削除される場合がある |
よくある間違い:容量の数値はブラウザのバージョンや設定によって変動するため、「〇〇MBまで確実に使える」と決め打ちで設計してしまうことです。余裕を持った容量設計を心がけましょう。
SafariのITPとプライベートブラウジングによる制限
SafariのITP(Intelligent Tracking Prevention、トラッキング防止機能)は、一定期間サイトへのアクセスがない場合にlocalStorageを含むスクリプトによる保存データを削除することがあります。また、プライベートブラウジングモードでは、通常モードと保存領域が分離されるか、容量が大幅に制限される場合があります。
// プライベートモードなどでsetItemが失敗する可能性を考慮する
try {
localStorage.setItem('test', '1');
localStorage.removeItem('test');
} catch (e) {
console.warn('この環境ではlocalStorageが制限されています');
}
つまずきやすいポイント:「Safariだけデータが消える」という報告を受けた際、バグだと思い込んで実装を疑う前に、ITPやプライベートモードの仕様である可能性を確認しましょう。
PWA・WebView・アプリ内ブラウザで保存データが消えるケース
PWA(Webアプリをネイティブアプリのように利用できる仕組み)やSNSアプリ内蔵のWebView(アプリ内ブラウザ)は、通常のブラウザとは異なるストレージ管理ポリシーを持つことがあります。特にiOSでは、PWAとSafari本体でストレージが共有されない場合があり、アプリ内ブラウザでは容量制限が厳しく設定されていることもあります。
よくある間違い:「ホーム画面に追加したPWAでログインし直しになる」という現象は、実装のバグではなく、ストレージ領域がSafari本体と分離されていることが原因である場合が多いです。
キャッシュクリア・Cookie削除・SameSite属性の影響範囲
ユーザーがブラウザの「閲覧履歴とCookieを削除」する操作を行うと、多くのブラウザではlocalStorageも同時に削除対象となります。一方で、SameSite属性はCookieに関する仕様であり、localStorage自体の挙動には直接影響しません。
| 操作・設定 | localStorageへの影響 |
|---|---|
| Cookie削除(単体) | ブラウザにより影響する場合としない場合がある |
| 「閲覧データを削除」(履歴・Cookie・キャッシュ一括) | 多くの場合、localStorageも削除される |
SameSite属性の設定変更 | localStorageには影響しない(Cookie専用の仕様) |
つまずきやすいポイント:SameSiteはCookieのクロスサイト送信を制御する仕組みであり、localStorageの削除や保護には関与しません。両者を混同しないようにしましょう。

localStorageが使えないときの代替手段と使い分け
localStorageが使えない、または適さない場面では、用途に応じて「Cookie」「sessionStorage」「IndexedDB」「サーバーサイド保存」を使い分けるのが基本方針です。保存期間・容量・サーバーとの連携有無を基準に選択します。
Cookie・sessionStorage・localStorageの違いと選び方
Cookieはサーバーへの自動送信が必要な小さなデータ、sessionStorageはタブを閉じるまでの一時的なデータ、localStorageはタブを閉じても消えない永続的なデータの保存に向いています。
| 項目 | Cookie | sessionStorage | localStorage |
|---|---|---|---|
| 保存容量の目安 | 約4KB | オリジンごと約5〜10MB | オリジンごと約5〜10MB |
| 有効期限 | 設定した期限まで(明示的に指定可能) | タブ・ウィンドウを閉じるまで | 明示的に削除するまで半永久的 |
| サーバーへの自動送信 | あり(リクエストごとに送信) | なし | なし |
| 主な用途 | 認証トークン、セッションID | フォームの一時保持、タブ単位の状態管理 | ユーザー設定、ログイン状態の保持など |
よくある間違い:機密性の高い認証トークンをlocalStorageに保存してしまうことです。localStorageはJavaScriptから自由に読み取れるため、XSS(クロスサイトスクリプティング)攻撃のリスクを考慮し、認証情報の扱いには注意が必要です。
IndexedDBを使って大容量データを保存する方法
IndexedDBは、ブラウザに搭載されたNoSQL型のデータベースで、localStorageよりもはるかに大きな容量(数百MB〜数GB規模)を扱え、オブジェクトをそのまま保存できるのが特徴です。画像データや大量の一覧データを扱う場合に適しています。
const request = indexedDB.open('myDatabase', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
db.createObjectStore('users', { keyPath: 'id' });
};
request.onsuccess = (event) => {
const db = event.target.result;
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
store.put({ id: 1, name: 'taro' });
};
つまずきやすいポイント:IndexedDBは非同期APIであるため、localStorageのような同期処理の感覚でコードを書くと、データ取得前に後続処理が実行されてしまうことがあります。onsuccessなどのコールバックやPromiseラッパーを使った制御が必須です。
ログイン情報やユーザー設定をサーバーサイドで永続化する方法
複数端末での同期が必要なデータや、機密性の高い情報は、ブラウザのストレージではなくサーバー側のデータベースで管理するのが安全です。クライアントは認証済みのAPIリクエストを通じてデータを取得・保存し、ブラウザ側には最小限の識別情報のみを保持します。
// サーバーに設定を保存する例
async function saveUserSettings(settings) {
await fetch('/api/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(settings),
});
}
よくある間違い:「サーバー保存に切り替えれば安心」と考え、通信エラー時のフォールバック処理を用意しないことです。オフライン時や通信失敗時の挙動も設計に含めましょう。
よくある質問(FAQ)
-
モバイルブラウザ(スマートフォン)でもlocalStorageの挙動はPCと同じですか?
-
基本的な仕様はPCと同じですが、iOSのSafariではITPの影響を受けやすく、一定期間アクセスがないとデータが削除される場合があります。Android版Chromeなどは、PC版とほぼ同様の挙動になります。
-
localStorageの容量の目安はどれくらいですか?
-
安全な下限値として「オリジンごと5MB程度」が目安となります。ブラウザやバージョンによってはそれ以上の保存が可能な場合もありますが、正確な数値を保証する仕様はないため、5MBを上限の基準とした余裕を持ったデータ設計が推奨されます
-
プライベートブラウジング(シークレットモード)では何時間くらいデータが保持されますか?
-
「何時間」という明確な仕様は定義されておらず、多くのブラウザではプライベートウィンドウ・タブを閉じた時点でデータが破棄されます。時間経過ではなく「ウィンドウを閉じるまで」が基準になる点に注意してください。
-
localStorageに保存したデータは暗号化されていますか?
-
いいえ、localStorageのデータは平文でブラウザに保存され、暗号化は行われません。パスワードやトークンなどの機密情報をそのまま保存するのは避け、必要であればサーバー側での管理を検討してください。
-
localStorageとCookieでSameSite属性のような送信制御はできますか?
-
できません。
SameSite属性はCookie固有の仕様であり、localStorageにはクロスサイトでの送信という概念自体が存在しません。localStorageは常に同一オリジン内でのみ読み書き可能です。
まとめ
localStorageにアクセスできない問題は、エラーメッセージと発生環境を切り分けることでほとんどの原因を特定できます。最後に要点を整理します。
localStorage is not definedはブラウザ外(Node.jsやSSR処理中)での実行が原因SecurityErrorはfile://実行やストレージブロック設定などセキュリティ条件が原因QuotaExceededErrorは保存容量の上限超過が原因- Next.jsなどSSR環境では
typeof window !== 'undefined'とuseEffect()(VueならonMounted())でクライアント側限定の処理にする - 本番でデータが消える場合は、オリジンの変更・Safariの ITP・プライベートモード・PWA特有のストレージ分離を疑う
- 大容量データはIndexedDB、機密情報や複数端末同期が必要なデータはサーバーサイド保存への切り替えを検討する
まずはtry...catchとDevToolsで原因を特定し、恒常的に容量や機密性の課題がある場合は、IndexedDBやサーバーサイド保存への移行を早めに検討しましょう。




