Featured image of post localStorageの容量・同期問題を解決するIndexedDBとlocalForageライブラリ Featured image of post localStorageの容量・同期問題を解決するIndexedDBとlocalForageライブラリ

localStorageの容量・同期問題を解決するIndexedDBとlocalForageライブラリ

localStorageの容量制限と同期処理問題を解決するIndexedDBとlocalForageライブラリの活用法を解説。5MB制限や描画ブロックに悩まず、非同期で大容量のクライアントストレージを快適に使うための比較と実装テクニックを紹介します。

はじめに

Webアプリケーションのクライアント側ストレージは、オフラインキャッシュやユーザー設定、下書きデータ、セッション状態の保持に欠かせません。長らくlocalStorageが最もシンプルな選択肢でしたが、アプリケーションの大規模化に伴いその限界が顕在化しています。本記事ではlocalStorage、生のIndexedDB、そしてlocalForageラッパーライブラリを比較し、適切なストレージ選択の指針を提供します。


localStorageの限界

localStorageは同期的なキー・バリューAPI(setItem / getItem)を提供しますが、以下の強い制約があります。

制約詳細
最大容量オリジンあたり約5 MB(ブラウザにより変動)
同期的API読み書き中はメインスレッドをブロック
文字列のみオブジェクトの保存にはJSONシリアライズが必須
索引機能なしデータのクエリやフィルタが不可能

SPAで大規模なオフラインキャッシュや複雑なアプリケーション状態を扱うと、5 MBの壁にすぐに到達します。また同期書き込みによるレンダリングブロックは、特に低性能端末でユーザー体験を著しく損ねます。


IndexedDB: 高性能だが複雑

IndexedDBはブラウザ組み込みのNoSQLデータベースです。以下の特長を持ちます。

  • 事実上無制限のストレージ(ブラウザ依存、通常数百MB〜GB)
  • 完全非同期API — UIスレッドをブロックしない
  • 構造化データ — オブジェクト、Blob、配列をそのまま保存可能
  • インデックスによる効率的な検索

しかし生のIndexedDBはコード量が非常に多いという問題があります。

const request = indexedDB.open('MyDatabase', 1);
request.onsuccess = (event) => {
  const db = event.target.result;
  const tx = db.transaction('store', 'readonly');
  const store = tx.objectStore('store');
  const get = store.get('myKey');
  get.onsuccess = () => console.log(get.result);
};

単純なキー・バリュー操作にもかかわらず複数のイベントハンドラが必要で、初心者には敷居が高いAPIです。


localForage: 両方の良さを活かす

localForageはIndexedDB(フォールバックとしてWebSQLおよびlocalStorage)を、localStorageのようなシンプルな非同期APIでラップする軽量ライブラリです。利用可能な最適なストレージエンジンを自動選択します。

npm install localforage

基本的な使い方

import localforage from 'localforage';

// 保存(オブジェクトやBlobもそのまま保存可能)
await localforage.setItem('user', { name: '山田', role: 'admin' });

// 読み取り
const user = await localforage.getItem('user');
console.log(user.name); // "山田"

// 削除
await localforage.removeItem('user');

// 全削除
await localforage.clear();

PromiseベースのAPIで、async/awaitと相性が良く、コードが簡潔になります。

設定例

localforage.config({
  driver: [localforage.INDEXEDDB, localforage.LOCALSTORAGE],
  name: 'MyApp',
  storeName: 'app_cache',
});

フォールバックの仕組み

localForageは以下の優先順位でドライバを自動選択します。

  1. IndexedDB — 容量・非同期性能ともに最優先
  2. WebSQL — 非推奨だが一部ブラウザで利用可能
  3. localStorage — 最終手段、5 MB制限あり

実例: APIレスポンスのキャッシュ

import localforage from 'localforage';

const CACHE_TTL = 5 * 60 * 1000; // 5分

async function fetchWithCache(url) {
  const cached = await localforage.getItem(url);
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.data;
  }
  const response = await fetch(url);
  const data = await response.json();
  await localforage.setItem(url, { data, timestamp: Date.now() });
  return data;
}

このパターンによりネットワークリクエストを削減しつつ、UIの応答性を維持できます。


適切なストレージの選び方

ユースケース推奨ストレージ
単純なキー・バリュー、小容量(5 MB未満)localStorage / localForage
大規模JSONキャッシュ、オフラインデータlocalForage(IndexedDBバックエンド)
複雑なクエリ、インデックス検索生のIndexedDB
タブ間データ同期IndexedDB + BroadcastChannel
一時的なセッション情報sessionStorage

まとめ

localStorageはごく小規模なデータには今でも有効ですが、同期的な制約と5 MBという容量制限から、データ量の多いモダンなアプリケーションには不向きです。生のIndexedDBは強力ですが、キー・バリュー用途には過剰に複雑です。localForageはPromiseベースのクリーンなAPIでIndexedDBの機能を活かし、環境に応じてgracefulにフォールバックします。ほとんどのクライアントストレージ要件において、localForageは実用的な選択肢となるでしょう。