Работа с браузерным хранилищем (LocalStorage, IndexedDB) для офлайн-режима - Компьюти
Logo КОМПЬЮТИ

Работа с браузерным хранилищем (LocalStorage, IndexedDB) для офлайн-режима

Время на прочтение: 4 мин.

Офлайн-режим давно перестал быть исключительно функцией мобильных приложений. Пользователь может потерять интернет в метро, на производстве, в дороге или просто столкнуться с нестабильным соединением. Если веб-приложение в этот момент полностью зависит от сервера, часть функций становится недоступной.

Браузерное хранилище позволяет решить эту проблему: сохранить необходимые данные на устройстве пользователя и продолжить работу даже при временном отсутствии сети.

В этой статье разберём два основных механизма — LocalStorage и IndexedDB, сравним их возможности и покажем, как использовать их при разработке офлайн-режима.

Зачем веб-приложению офлайн-режим

Типичная веб-система работает по принципу:

пользователь → браузер → сервер → база данных → ответ пользователю.

При отсутствии интернета цепочка обрывается. Но далеко не всем операциям действительно нужен сервер прямо сейчас.

Например, в CRM пользователь может:

  • открыть ранее загруженный список клиентов;
  • посмотреть сохранённые документы;
  • создать черновик;
  • внести изменения в форму;
  • продолжить работу с уже загруженными данными.

Отправить изменения на сервер можно позже — когда соединение восстановится.

Именно здесь появляется концепция offline-first: приложение старается использовать локальные данные в первую очередь, а сервер синхронизируется с ними при наличии сети.

LocalStorage: простой вариант для небольших данных

localStorage — встроенное браузерное хранилище, которое позволяет сохранять данные в формате «ключ → значение».

Например:

localStorage.setItem('theme', 'dark');

const theme = localStorage.getItem('theme');

localStorage.removeItem('theme');

Все значения хранятся как строки. Поэтому объекты обычно приходится сериализовать:

const settings = {
  language: 'ru',
  notifications: true
};

localStorage.setItem('settings', JSON.stringify(settings));

const savedSettings = JSON.parse(
  localStorage.getItem('settings')
);

Где LocalStorage подходит хорошо

Это удобный вариант для небольших настроек и простых состояний:

  • тема оформления;
  • выбранный язык;
  • состояние отдельных переключателей;
  • небольшие пользовательские настройки;
  • простые флаги;
  • черновики небольшого размера.

Например, форму можно автоматически сохранять после каждого изменения:

const input = document.querySelector('#comment');

input.value = localStorage.getItem('comment') || '';

input.addEventListener('input', () => {
  localStorage.setItem('comment', input.value);
});

Пользователь закроет вкладку, вернётся позже — и введённый текст останется.

Ограничения LocalStorage

Несмотря на простоту, использовать localStorage как полноценную локальную базу данных не стоит.

Основные ограничения:

  1. Небольшой объём хранения. Точный лимит зависит от браузера и окружения.
  2. Только строки. Для сложных структур требуется сериализация.
  3. Синхронные операции. Работа с хранилищем выполняется в основном потоке браузера.
  4. Нет полноценной системы запросов и индексов.
  5. Не подходит для больших наборов данных.

Если приложение должно хранить тысячи объектов, изображения, документы или сложное состояние — лучше рассмотреть IndexedDB.

IndexedDB: локальная база данных в браузере

IndexedDB — асинхронное хранилище для структурированных данных.

В отличие от localStorage, оно рассчитано на значительно более сложные сценарии. Можно хранить объекты, создавать индексы и выполнять поиск по данным.

Условно архитектуру можно представить так:

IndexedDB
├── customers
├── orders
├── products
└── syncQueue

Например, интернет-магазин может сохранить каталог товаров локально:

const request = indexedDB.open('ShopDB', 1);

request.onupgradeneeded = (event) => {
  const db = event.target.result;

  db.createObjectStore('products', {
    keyPath: 'id'
  });
};

После создания базы приложение может записывать данные:

const transaction = db.transaction(
  'products',
  'readwrite'
);

const store = transaction.objectStore('products');

store.put({
  id: 101,
  name: 'Ноутбук',
  price: 999
});

И затем получать их без обращения к серверу.

LocalStorage или IndexedDB?

Выбор зависит прежде всего от объёма и структуры данных.

КритерийLocalStorageIndexedDB
ПростотаВысокаяСредняя
ФорматСтрокиСтруктурированные данные
АсинхронностьНетДа
Сложные объектыНеудобноПоддерживаются
Индексы и поискНетДа
Большие объёмыНе лучший вариантПодходит лучше
Простые настройкиОтличноИзбыточно
Offline-first приложениеОграниченноХороший вариант

Практическое правило простое:

LocalStorage — для небольших настроек. IndexedDB — для данных приложения.

Как построить офлайн-режим

Само наличие локального хранилища ещё не делает приложение офлайн-доступным. Необходимо продумать архитектуру.

Один из распространённых вариантов выглядит так:

                 ┌──────────────┐
                 │  Интерфейс   │
                 └──────┬───────┘
                        │
                        ▼
                 ┌──────────────┐
                 │ Local/Domain │
                 │    layer     │
                 └──────┬───────┘
                        │
              ┌─────────┴─────────┐
              ▼                   ▼
       ┌──────────────┐    ┌──────────────┐
       │  IndexedDB   │    │     API      │
       └──────────────┘    └──────────────┘

Приложение сначала работает с локальным слоем данных. Если сеть доступна, изменения отправляются на сервер. Если сети нет — операция сохраняется локально и ставится в очередь синхронизации.

Очередь изменений

Предположим, пользователь редактирует карточку клиента без интернета.

Вместо ошибки можно сохранить операцию:

{
  type: 'UPDATE_CUSTOMER',
  entityId: 42,
  payload: {
    phone: '+49 ...'
  },
  createdAt: 1787832000000
}

Такие операции можно складывать в syncQueue.

Когда соединение восстанавливается:

1. Проверяем наличие сети
2. Получаем операции из syncQueue
3. Отправляем их на сервер
4. Получаем подтверждение
5. Удаляем успешно синхронизированные операции

Так пользователь продолжает работу независимо от качества соединения.

Что делать при конфликте данных

Офлайн-режим усложняется, когда одни и те же данные меняются одновременно на нескольких устройствах.

Например:

  • сотрудник A изменил заказ без интернета;
  • сотрудник B изменил тот же заказ на сервере;
  • устройство сотрудника A снова подключилось к сети.

Теперь приложение должно решить, какая версия данных актуальна.

В зависимости от продукта можно использовать разные стратегии:

Last Write Wins

Последнее изменение заменяет предыдущее.

Подходит для простых сценариев, но может привести к потере данных.

Версионирование

Каждая запись получает версию:

{
  id: 42,
  version: 7,
  name: 'Компания'
}

При синхронизации сервер проверяет, не устарела ли версия клиента.

Ручное разрешение конфликтов

Пользователю показываются обе версии и предлагается выбрать нужную.

Это сложнее с точки зрения интерфейса, но подходит для критически важных данных.

Не забываем про Service Worker

LocalStorage и IndexedDB отвечают прежде всего за хранение данных. Но полноценный офлайн-режим обычно требует ещё и кеширования ресурсов приложения.

Для этого используется Service Worker.

Он может перехватывать сетевые запросы и отдавать ранее сохранённые HTML, CSS, JavaScript и другие ресурсы.

В результате архитектура становится примерно такой:

              Пользователь
                   │
                   ▼
              Веб-приложение
                   │
          ┌────────┴────────┐
          ▼                 ▼
    Service Worker      IndexedDB
          │                 │
          ▼                 ▼
       Cache API        Данные
          │
          ▼
        Сервер

Service Worker отвечает за доступность самого приложения и его ресурсов, а IndexedDB — за локальные данные и состояние.

Типичные ошибки

Хранить всё в LocalStorage

Большой JSON-объект со всем состоянием приложения быстро превращается в источник проблем.

Лучше разделять настройки и данные:

  • настройки → LocalStorage;
  • бизнес-данные → IndexedDB.

Считать navigator.onLine гарантией наличия интернета

Состояние online не означает, что API действительно доступно. Между браузером и сервером могут возникнуть проблемы с DNS, VPN, прокси или самим сервером.

Надёжнее учитывать реальные ошибки сетевых запросов.

Не продумывать синхронизацию заранее

Добавить IndexedDB в уже готовое приложение недостаточно. Нужно определить:

  • какие данные кешируются;
  • какие операции разрешены офлайн;
  • как формируется очередь изменений;
  • как определяется успешная синхронизация;
  • что происходит при конфликте.

Хранить чувствительные данные без необходимости

Локальное хранилище не следует рассматривать как защищённое хранилище секретов. Не стоит без необходимости помещать туда пароли, токены и другие критически важные данные.

Какой подход выбрать

Для небольшого сайта может быть достаточно localStorage:

Настройки
   ↓
LocalStorage

Для полноценного веб-приложения:

Данные приложения
       ↓
   IndexedDB
       ↓
Очередь изменений
       ↓
  Синхронизация
       ↓
    Backend

А если требуется полноценный PWA:

                 ┌───────────────┐
                 │ Web App / PWA │
                 └───────┬───────┘
                         │
             ┌───────────┼───────────┐
             ▼           ▼           ▼
        Cache API    IndexedDB   Network API
             │           │           │
             └───────────┴───────────┘
                         │
                         ▼
                       Server

Итог

Офлайн-режим — это не просто возможность «сохранить данные в браузере». Это архитектурный подход, в котором приложение умеет работать с локальным состоянием, переживать отсутствие сети и синхронизировать изменения после восстановления соединения.

LocalStorage хорошо подходит для небольших настроек и простых значений. IndexedDB — более подходящий инструмент для хранения больших объёмов структурированных данных. А вместе с Service Worker и механизмом синхронизации они позволяют построить полноценное offline-first веб-приложение.

При разработке такого функционала важно начинать не с выбора API, а с бизнес-сценариев: что пользователь должен иметь возможность делать без интернета, какие данные ему нужны локально и как приложение будет синхронизировать изменения после возвращения сети.

Именно эти решения определяют, насколько надёжным и удобным получится офлайн-режим.


Читайте также:

Приглашаем поучаствовать в обсуждении статьи:

Комментариев 0