ブラウザにデータを保存する方法は多数あります。お客様のニーズに最適な方法はどれですか?
外出中はインターネット接続が不安定であったり存在しなかったりする場合があります。このような理由から、オフライン サポートと信頼性の高いパフォーマンスがプログレッシブ ウェブアプリにおける一般的な特徴となっています。完全なワイヤレス環境であっても、キャッシュやその他のストレージ テクニックを適切に利用すれば、ユーザー エクスペリエンスを大幅に向上させることができます。静的アプリリソース(HTML、JavaScript、CSS、画像など)とデータ(ユーザーデータ、ニュース記事など)をキャッシュする方法はいくつかあります。では、最適なソリューションはどれでしょうか?保存できるデータの量はどのくらいですか?強制排除を防ぐにはどうすればよいですか?
何を使用すればよいですか?
リソースの保存に関する一般的な推奨事項は次のとおりです。
- アプリの読み込みに必要なネットワーク リソースには、Cache Storage API(Service Worker の一部)を使用します。
- ファイルベースのコンテンツの場合は、オリジンのプライベート ファイル システム(OPFS)を使用します。
- その他のデータには、IndexedDB(promises ラッパーを使用)を使用します。
IndexedDB、OPFS、Cache Storage API は、すべての最新ブラウザでサポートされています。非同期であり、メインスレッドをブロックしません(ただし、ウェブワーカーでのみ使用できる OPFS の同期バリアントもあります)。window
オブジェクト、ウェブワーカー、サービス ワーカーからアクセスできるため、コードのどこでも使用できます。
その他のストレージ メカニズムについて
ブラウザでは他にもいくつかのストレージ メカニズムを使用できますが、使用が制限されており、パフォーマンスの問題を引き起こす可能性があります。
SessionStorage はタブ固有で、タブの存続期間にスコープが設定されます。IndexedDB キーなど、少量のセッション固有の情報を保存する場合に便利です。同期的であり、メインスレッドをブロックするため、慎重に使用する必要があります。サイズは約 5 MB に制限され、文字列のみを含めることができます。タブ固有であるため、ウェブワーカーやサービスワーカーからはアクセスできません。
LocalStorage は同期的であり、メインスレッドをブロックするため、使用しないことをおすすめします。サイズは約 5 MB に制限され、文字列のみを含めることができます。Web Worker や Service Worker から LocalStorage にアクセスすることはできません。
Cookie には用途がありますが、保存には使用しないでください。Cookie は HTTP リクエストごとに送信されるため、少量を超えるデータを保存すると、すべてのウェブリクエストのサイズが大幅に増加します。同期であり、Web Worker からアクセスできません。LocalStorage や SessionStorage と同様に、Cookie は文字列のみに制限されます。
File System Access API は、ユーザーがローカル ファイル システム上のファイルを読み取り、編集できるように設計されています。ページがローカル ファイルの読み取りまたは書き込みを行うには、ユーザーが権限を付与する必要があります。また、ファイルハンドルが IndexedDB にキャッシュに保存されている場合を除き、権限はセッション間で保持されません。File System Access API は、ファイルを開いて変更し、必要に応じて変更をファイルに保存する必要があるエディタなどのユースケースに最適です。
File System API と FileWriter API には、サンドボックス化されたファイル システムへのファイルの読み取りと書き込みを行うメソッドが用意されています。非同期ですが、Chromium ベースのブラウザでのみ使用可能であるため、推奨されません。
保存できる量
つまり、非常に多くの、少なくとも数百 MB、場合によっては数百 GB 以上の容量が必要です。ブラウザの実装はさまざまですが、使用可能なストレージの量は通常、デバイスで使用可能なストレージの量に基づいています。
- Chrome では、ブラウザが合計ディスク容量の最大 80% を使用できます。オリジンは、合計ディスク容量の最大 60% を使用できます。StorageManager API を使用して、使用可能な最大割り当てを特定できます。他の Chromium ベースのブラウザでは異なる場合があります。
- シークレット モードでは、Chrome はオリジンが使用できるストレージの量を合計ディスク容量の約 5% に減らします。
- Chrome で [すべてのウィンドウを閉じると Cookie とサイトデータを削除する] を有効にしている場合、保存容量の割り当ては最大で約 300 MB に大幅に減らされます。
- Firefox では、ブラウザが空きディスク容量の最大 50% を使用できます。eTLD+1 グループ(例:
example.com
、www.example.com
、foo.bar.example.com
)は、最大 2 GB を使用できます。StorageManager API を使用して、残りの空き容量を確認できます。 - Safari(パソコンとモバイルの両方)では、約 1 GB まで許可されているようです。上限に達すると、Safari からユーザーにプロンプトが表示され、上限が 200 MB 単位で増加します。公式のドキュメントは見つかりませんでした。
- モバイル Safari のホーム画面に PWA を追加すると、新しいストレージ コンテナが作成され、PWA とモバイル Safari の間で共有されるものはなくなります。インストールされた PWA の割り当てに達すると、追加の保存容量をリクエストする方法はないようです。
これまでは、サイトが保存するデータのしきい値を超えると、ブラウザから、さらにデータを使用するための権限を付与するようユーザーに求めるメッセージが表示されていました。たとえば、オリジンが 50 MB を超える場合、ブラウザは最大 100 MB の保存を許可するようユーザーにプロンプトを表示し、50 MB ずつ増やして再度プロンプトを表示します。
現在、ほとんどの最新ブラウザではユーザーにプロンプトは表示されず、サイトは割り当てられた割り当て量まで使用できます。例外は Safari で、ストレージ割り当てが超過すると、割り当てられた割り当ての増加を許可するよう求めるメッセージが表示されます。オリジンが割り当てられた割り当てを超えて使用しようとすると、データの書き込みの試行は失敗します。
空き容量を確認するにはどうすればよいですか?
多くのブラウザでは、StorageManager API を使用して、オリジンで使用可能なストレージの量と、使用しているストレージの量を特定できます。IndexedDB と Cache API によって使用されるバイト数の合計が報告され、残りの使用可能な保存容量の概算を計算できます。
if (navigator.storage && navigator.storage.estimate) {
const quota = await navigator.storage.estimate();
// quota.usage -> Number of bytes used.
// quota.quota -> Maximum number of bytes available.
const percentageUsed = (quota.usage / quota.quota) * 100;
console.log(`You've used ${percentageUsed}% of the available storage.`);
const remaining = quota.quota - quota.usage;
console.log(`You can write up to ${remaining} more bytes.`);
}
割り当て超過エラーをキャッチする必要があります(下記を参照)。使用可能な割り当てが、使用可能な実際のストレージ量を超える場合があります。
検証
開発中は、ブラウザの DevTools を使用してさまざまなストレージ タイプを検査し、保存されているすべてのデータを消去できます。
Chrome 88 では、[ストレージ] ペインでサイトのストレージ クォータをオーバーライドできる新機能が追加されました。この機能を使用すると、さまざまなデバイスをシミュレートし、ディスク可用性が低いシナリオでアプリの動作をテストできます。[アプリケーション]、[ストレージ] の順に移動し、[カスタムの保存容量をシミュレート] チェックボックスをオンにして、有効な数値を入力して保存容量をシミュレートします。
このガイドの作成中に、できるだけ多くのストレージをすばやく使用しようとするシンプルなツールを作成しました。さまざまなストレージ メカニズムを試し、割り当てをすべて使用した場合にどうなるかを簡単に確認できます。
割り当て超過を処理する方法
割り当てを超過した場合、どうすればよいですか?最も重要なことは、QuotaExceededError
であれ他であれ、書き込みエラーは常にキャッチして処理することです。その後、アプリの設計に応じて、その処理方法を決定します。たとえば、長期間アクセスされていないコンテンツを削除したり、サイズに基づいてデータを削除したり、ユーザーが削除する内容を選択できるようにしたりします。
IndexedDB と Cache API の両方で、使用可能な割り当てを超えると、QuotaExceededError
という名前の DOMError
がスローされます。
IndexedDB
オリジンの割り当てを超えている場合、IndexedDB への書き込みは失敗します。トランザクションの onabort()
ハンドラが呼び出され、イベントが渡されます。イベントの error プロパティに DOMException
が含まれます。エラー name
をチェックすると、QuotaExceededError
が返されます。
const transaction = idb.transaction(['entries'], 'readwrite');
transaction.onabort = function(event) {
const error = event.target.error; // DOMException
if (error.name == 'QuotaExceededError') {
// Fallback code goes here
}
};
Cache API
オリジンが割り当てを超えている場合、Cache API への書き込みの試行は QuotaExceededError
DOMException
で拒否されます。
try {
const cache = await caches.open('my-cache');
await cache.add(new Request('/sample1.jpg'));
} catch (err) {
if (error.name === 'QuotaExceededError') {
// Fallback code goes here
}
}
エビクションの仕組み
ウェブ ストレージは、「ベスト エフォート」と「永続」の 2 つのバケットに分類されます。ベスト エフォートとは、ユーザーを中断することなくブラウザによってストレージを消去できることを意味しますが、長期的または重要なデータに対しては永続性が低くなります。永続ストレージは、ストレージが少なくなっても自動的に消去されることはありません。ユーザーが(ブラウザ設定により)手動でこのストレージを消去する必要があります。
デフォルトでは、サイトのデータ(IndexedDB、Cache API など)はベスト エフォート カテゴリに分類されます。つまり、サイトが永続ストレージをリクエストしていない限り、デバイスのストレージ容量が少ないときなど、ブラウザは独自の判断でサイトデータを強制排除することがあります。
ベスト エフォートのエビクション ポリシーは次のとおりです。
- Chromium ベースのブラウザは、ブラウザの空き容量が不足すると、最も長い間使用されていないオリジンから順に、ブラウザの容量が制限を超えなくなるまで、すべてのサイトデータを削除します。
- Firefox は、使用可能なディスク容量がいっぱいになると、最も長く使用されていないオリジンから順に、ブラウザの容量が制限を超えなくなるまで、すべてのサイトデータを削除します。
- Safari では以前はデータを強制排除しませんでしたが、最近、書き込み可能なすべてのストレージに 7 日間の上限を新たに実装しました(下記を参照)。
iOS および iPadOS 13.4 以降、および macOS の Safari 13.1 以降では、IndexedDB、Service Worker の登録、Cache API など、すべてのスクリプト書き込み可能なストレージに 7 日間の上限が設定されています。つまり、ユーザーがサイトを操作しなかった場合、Safari の使用から 7 日後に Safari はキャッシュからすべてのコンテンツを削除します。この強制排除ポリシーは、ホーム画面に追加されたインストール済みの PWA には適用されません。詳しくは、WebKit ブログのサードパーティ Cookie の完全なブロックとその他の機能をご覧ください。
ストレージ バケット
Storage Buckets API のコアとなる考え方は、サイトに複数のストレージ バケットを作成する機能を付与することです。ブラウザは、各バケットを他のバケットから独立して削除できます。これにより、デベロッパーは強制排除の優先順位を指定して、最も価値の高いデータが削除されないようにすることができます。
ボーナス: IndexedDB のラッパーを使用する理由
IndexedDB は低レベルの API であり、使用前に大幅な設定が必要になります。これは、複雑度の低いデータを保存する場合に特に面倒です。ほとんどの最新の Promise ベースの API とは異なり、イベントベースです。IndexedDB の idb などの Promise ラッパーは、強力な機能の一部を隠しますが、さらに重要なのは、IndexedDB ライブラリに付属する複雑な機構(トランザクション、スキーマ バージョニングなど)を隠すことです。
ボーナス: SQLite Wasm
Web SQL が非推奨になり Chrome から削除された後、Google は一般的な SQLite データベースのメンテナンス担当者と協力して、SQLite ベースの Web SQL の代替手段を提供しました。使用方法の詳細については、Origin プライベート ファイル システムを基盤とするブラウザでの SQLite Wasm をご覧ください。
まとめ
ストレージ容量が限られており、ユーザーにデータの保存を促す時代は終わりました。サイトは、実行に必要なすべてのリソースとデータを効果的に保存できます。StorageManager API を使用すると、使用可能な容量と使用済みの容量を確認できます。また、永続ストレージを使用すると、ユーザーが削除しない限り、強制排除から保護できます。
参考情報
ありがとう
このガイドの確認に協力してくれた Jarryd Goodman、Phil Walton、Eiji Kitamura、Daniel Murphy、Darwin Huang、Josh Bell、Marijn Kruisselbrink、Victor Costan に感謝します。この記事の元となった記事を執筆した Eiji Kitamura、Addy Osmani、Marc Cohen に感謝します。Eiji は、現在の動作の検証に役立つ Browser Storage Abuser という便利なツールを作成しました。できるだけ多くのデータを保存し、ブラウザのストレージの上限を確認できます。Safari を詳しく調査してストレージの上限を把握した François Beaufort 氏、オリジンの非公開ファイル システム、ストレージ バケット、SQLite Wasm、2024 年の全体的なコンテンツの更新に関する情報を追加した Thomas Steiner 氏に感謝します。
ヒーロー画像は Unsplash の Guillaume Bolduc によるものです。