localForage опирается на браузерные механизмы хранения данных —
прежде всего IndexedDB, WebSQL (устаревший) и localStorage. Это
означает, что выполнение кода вне браузера или в частично
инициализированной среде может привести к ошибкам доступа к
window, отсутствию драйверов или некорректной инициализации
асинхронного хранилища.
Типовые проблемные сценарии:
window и
document отсутствуютКорректная архитектура работы с localForage начинается не с вызова
методов setItem/getItem, а с определения доступности среды
и выбора стратегии инициализации.
Паттерн проверки окружения заключается в разделении логики на два слоя:
Такой подход исключает:
Первый и самый простой шаг — проверка существования
window:
const isBrowser = typeof window !== 'undefined';
Эта проверка решает только проблему SSR и Node.js, но не гарантирует доступность IndexedDB.
Более строгая версия:
const isBrowser = () =>
typeof window !== 'undefined' &&
typeof window.indexedDB !== 'undefined';
Однако даже наличие indexedDB не гарантирует корректную
работу (например, Safari в приватном режиме).
localForage предоставляет встроенную возможность проверки поддержки
через driver() и setDriver().
driver()import localforage from "localforage";
async function isStorageAvailable() {
try {
await localforage.setItem("__test__", "ok");
await localforage.removeItem("__test__");
return true;
} catch (e) {
return false;
}
}
Этот подход проверяет реальную работоспособность, а не только наличие API.
setDriverlocalForage позволяет явно задать драйвер:
localforage.INDEXEDDBlocalforage.WEBSQLlocalforage.LOCALSTORAGEПаттерн устойчивой инициализации:
import localforage from "localforage";
async function initStorage() {
if (typeof window === "undefined") {
return null;
}
try {
await localforage.setDriver([
localforage.INDEXEDDB,
localforage.WEBSQL,
localforage.LOCALSTORAGE
]);
return localforage;
} catch (e) {
return null;
}
}
Здесь реализуется принцип деградации: от IndexedDB к более простым механизмам.
В архитектуре SSR (Next.js, Nuxt, Remix) важно не импортировать и не инициализировать storage на сервере.
Паттерн ленивой загрузки:
let lfInstance = null;
export async function getStorage() {
if (typeof window === "undefined") {
return null;
}
if (lfInstance) {
return lfInstance;
}
const localforage = (await import("localforage")).default;
await localforage.setDriver([
localforage.INDEXEDDB,
localforage.LOCALSTORAGE
]);
lfInstance = localforage;
return lfInstance;
}
Ключевая идея — динамический import только на клиенте.
Чтобы исключить повторение проверок, строится слой-обёртка:
export class SafeStorage {
constructor(instance) {
this.instance = instance;
}
static async create() {
const lf = await getStorage();
return new SafeStorage(lf);
}
async set(key, value) {
if (!this.instance) return null;
return this.instance.setItem(key, value);
}
async get(key) {
if (!this.instance) return null;
return this.instance.getItem(key);
}
async remove(key) {
if (!this.instance) return null;
return this.instance.removeItem(key);
}
}
Такой слой гарантирует отсутствие runtime-ошибок при деградации окружения.
Антипаттерн:
if (navigator.userAgent.includes("Safari")) { ... }
Корректный подход:
async function supportsLocalForage() {
if (typeof window === "undefined") return false;
try {
const testKey = "__lf_test__";
await localforage.setItem(testKey, "1");
await localforage.removeItem(testKey);
return true;
} catch {
return false;
}
}
Feature detection всегда предпочтительнее эвристик по браузеру.
Если storage недоступен, система должна переключаться на:
Пример fallback:
class MemoryStorage {
constructor() {
this.store = new Map();
}
async setItem(key, value) {
this.store.set(key, value);
}
async getItem(key) {
return this.store.get(key) ?? null;
}
async removeItem(key) {
this.store.delete(key);
}
}
Интеграция с localForage:
let storage;
export async function init() {
const lf = await getStorage();
if (lf) {
storage = lf;
} else {
storage = new MemoryStorage();
}
return storage;
}
Даже при наличии IndexedDB возможны ошибки quota exceeded.
Паттерн предварительной проверки:
async function canWrite() {
try {
const key = "__quota_test__";
const payload = new Array(1000).fill("x").join("");
await localforage.setItem(key, payload);
await localforage.removeItem(key);
return true;
} catch (e) {
return false;
}
}
Жёсткая инициализация на уровне модуля приводит к ошибкам:
// плохо в SSR
const lf = localforage.createInstance({...});
Корректный подход — lazy factory:
export function createStorage() {
return {
async getInstance() {
if (typeof window === "undefined") return null;
const lf = await import("localforage");
return lf.default;
}
};
}
Приоритет драйверов должен задаваться явно:
await localforage.setDriver([
localforage.INDEXEDDB,
localforage.WEBSQL,
localforage.LOCALSTORAGE
]);
Паттерн окружения должен учитывать:
Оптимальная архитектура:
let readyPromise = null;
export function getReadyStorage() {
if (!readyPromise) {
readyPromise = (async () => {
if (typeof window === "undefined") return null;
const lf = (await import("localforage")).default;
try {
await lf.setDriver([
lf.INDEXEDDB,
lf.LOCALSTORAGE
]);
return lf;
} catch {
return null;
}
})();
}
return readyPromise;
}
Такой подход:
Проверка окружения перед использованием localForage строится на трёх уровнях:
Runtime detection
typeof windowCapability detection
Operational safety layer
Такая модель делает работу с localForage предсказуемой в SSR, браузерах с ограничениями и гибридных приложениях, где клиентская и серверная части сосуществуют в одном кодовом пространстве