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

Что такое Service Worker и как он вписывается в офлайн-логику

Service Worker — это скрипт, работающий в фоновом режиме между браузером и сетью. Он перехватывает сетевые запросы, управляет кэшем и может отвечать из локального хранилища, когда сеть недоступна.

Ключевая особенность в том, что Service Worker живёт отдельно от страницы: он запускается при событиях install, activate и fetch. Это даёт контроль над тем, что сохранять заранее, а что загружать динамически.

Основные стратегии кэширования и когда их применять

Стратегия выбирается в зависимости от требований: скорость загрузки, актуальность данных и устойчивость при отсутствии сети. Ниже приведены самые распространённые подходы и краткое объяснение их сильных и слабых сторон.

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

Стратегия Плюсы Минусы Когда использовать
Cache-first Мгновенная загрузка, экономия трафика Риск устаревшего контента Статика: стили, скрипты, изображения
Network-first Актуальные данные Медленнее при плохой сети Данные пользователей, API
Stale-while-revalidate Быстрый отклик + обновление в фоне Короткий период устаревания Новости, карточки товаров

Пресечение распространённых ошибок в стратегии

Частая ошибка — кэшировать всё подряд. Это приводит к переполнению и обслуживанию устаревших ресурсов. Нужно явно определять набор необходимых файлов и правила для динамических ответов.

Ещё одна промашка — отсутствие политики обновления. Без версионирования и удаления старых кэшей вы быстро получите неуправляемую коллекцию файлов. Лучше предусмотреть схему именования и механизмы уборки при activate.

Пример простого Service Worker: базовый шаблон

Ниже пример рабочего шаблона, который иллюстрирует install, activate и fetch. Он подходит для базовой офлайн-поддержки статических ресурсов.

// Название кэша
const CACHE_NAME = 'app-cache-v1';
const PRECACHE_URLS = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/offline.html'
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(PRECACHE_URLS))
      .then(() => self.skipWaiting())
  );
});

self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(keys => Promise.all(
      keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))
    ))
  );
  self.clients.claim();
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(resp => resp || fetch(event.request).catch(() => caches.match('/offline.html')))
  );
});

Этот код демонстрирует простой cache-first для заранее объявленных ресурсов и fallback на offline.html при ошибке сети. Для реальных проектов шаблон дорабатывают под нужные сценарии и API-запросы.

Предзагрузка и runtime-кэш: что к чему

Предзагрузка (precaching) хороша для ключевых файлов: оболочки приложения, критических стилей и страницы оффлайн. Эти файлы попадают в кэш во время install и доступны сразу после первого посещения.

Runtime-кэш управляет тем, что создаётся в ответ на пользовательские действия: изображения из ленты, ответы API и т.д. Здесь важнее гибкость и правила очистки, так как объём данных может расти.

Как правильно версионировать и чистить кэши

Простейшая практика — включать номер версии в имя кэша, например app-cache-v2. При обновлении Service Worker вы создаёте новый кэш и в событии activate удаляете старые версии.

Важно также избегать резких удалений, которые разоряют пользовательский опыт. Если вы кэшируете большие объёмы, делайте плавную миграцию или поэтапную очистку, чтобы не оставить приложение без необходимых ресурсов.

Офлайн для динамического контента: подходы и инструменты

Статические файлы проще — они предсказываемы. Динамические ответы API нуждаются в другом подходе: можно кэшировать JSON с политикой истечения, использовать stale-while-revalidate или хранить данные в IndexedDB. IndexedDB удобен для структурированных данных и больших объёмов.

Для сложных сценариев пригодятся библиотеки вроде Workbox. Она упрощает правила кэширования, маршрутизацию запросов и обновление стратегий, но не заменяет понимание логики офлайн-поддержки.

Fallback-страницы, ошибки и UX при отсутствии сети

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

Важно сохранять формы и действия пользователя офлайн с последующей синхронизацией. Background Sync помогает отправить накопленные данные, когда связь восстановится.

Обновления Service Worker и коммуникация с пользователем

Когда вы публикуете новую версию, Service Worker может установить её, но не активировать пока есть открытые вкладки старой версии. Обнаружение и аккуратное оповещение пользователя помогают избежать неожиданной смены UI прямо во время сессии.

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

Инструменты и отладка: где смотреть проблемы

DevTools в браузерах предоставляет вкладки для Service Worker, Cache Storage и Network. Там можно увидеть, какие файлы попали в кэш, воспроизвести отсутствие сети и отловить ошибки. Это первый и самый важный инструмент разработки.

Логи в консоли и тестирование в разных условиях сети — обязательны. На практике я несколько раз обнаруживал, что проблемы с CORS или неправильные заголовки мешали кешированию, и это легко заметить в панели сетевых запросов.

Мои наблюдения из практики

В одном из проектов мне пришлось сделать офлайн-поддержку для приложения заметок. Сначала мы кэшировали ответы API по cache-first и получили старые заметки после редактирования на другом устройстве. Перешли на stale-while-revalidate и решили проблему без потери скорости.

Другой случай — путешественнический журнал, где пользователи часто снимали фото в офлайне. Хранили метаданные в IndexedDB и сами изображения в кэше, что позволило загружать миниатюры мгновенно и синхронизировать полноразмерные файлы при появлении сети.

Практические советы перед развёртыванием

Проверьте заголовки кеширования на сервере и совместимость с HTTPS — Service Worker работает только по защищённому протоколу. Убедитесь, что файлы с версионными именами действительно меняются при обновлении, иначе пользователи останутся на старых копиях.

Тестируйте на реальных устройствах и в условиях плохой сети. Эмулировать отсутствие связи в DevTools удобно, но поведение в мобильных сетях может отличаться, особенно при переключениях между режимами энергосбережения.

Короткая сводка стратегий и когда их комбинировать

Комбинируйте стратегии: shell приложения кэшируйте заранее, API держите в stale-while-revalidate, а важные пользовательские данные — в network-first с fallback на локальную копию. Такая смесь даёт быстрый отклик и свежесть данных.

Не забудьте про лимиты и очистку: runtime-кэш лучше ограничивать по количеству элементов или общему размеру. Это предотвращает исчерпание пространства и снижение производительности.

Сервис-воркеры дают мощный, но одновременно тонкий инструмент: они решают проблему офлайна, но требуют дисциплины в управлении ресурсами и тестировании. Если следовать простым правилам — версионировать кэш, выбирать стратегию под тип контента и продумывать UX при отсутствии сети — вы получите надёжное приложение, которое не подведёт пользователя даже без интернета.