Офлайн-режим и офлайн-страница

Урок показывает, как собрать офлайн-режим: кеш оболочки и запасную офлайн-страницу.

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

Два уровня офлайна

Офлайн в PWA обычно строят в два слоя:

  • Кеш оболочки и ассетов. При установке Service Worker кешируем всё, что нужно для отрисовки интерфейса: HTML-каркас, CSS, JS, иконки. Тогда офлайн приложение хотя бы откроется.
  • Запасная офлайн-страница. Для запросов, которые нельзя отдать из кеша (например, страница, которую пользователь ещё не посещал), показываем дружелюбную страницу «Вы офлайн» вместо браузерной ошибки.

Кешируем оболочку на install

const CACHE = 'shell-v1';
const ASSETS = ['/', '/styles.css', '/app.js', '/offline.html', '/icons/icon-192.png'];

self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open(CACHE).then(function (cache) { return cache.addAll(ASSETS); })
  );
});

Запасная страница на fetch

В обработчике fetch для навигационных запросов (переходов по страницам) применяем network-first, а если сеть недоступна и в кеше ничего нет — отдаём /offline.html:

self.addEventListener('fetch', function (event) {
  if (event.request.mode === 'navigate') {
    event.respondWith(
      fetch(event.request).catch(function () {
        return caches.match(event.request)
          .then(function (c) { return c || caches.match('/offline.html'); });
      })
    );
  }
});

Простая офлайн-страница

<!DOCTYPE html>
<html lang="ru">
<head><meta charset="utf-8"><title>Нет сети</title></head>
<body>
  <h1>Вы офлайн</h1>
  <p>Проверьте подключение к интернету и попробуйте снова.</p>
</body>
</html>

Эта страница тоже должна быть в кеше (мы добавили её в ASSETS), иначе офлайн её неоткуда взять.

Как работает под капотом проверка онлайна

Браузер сообщает о состоянии сети через navigator.onLine и события online / offline. Но полагаться только на них рискованно: navigator.onLine === true означает лишь «есть сетевой интерфейс», а не «сервер доступен». Поэтому надёжнее реальная попытка fetch с catch: если запрос упал — считаем, что офлайн, и отдаём запасной ответ. Именно так устроен пример выше.

Частые ошибки

  • Забыть закешировать саму офлайн-страницу. Тогда в офлайне Service Worker не сможет её отдать.
  • Кешировать только HTML без CSS/JS. Страница откроется «голой» — без стилей и скриптов.
  • Полагаться на navigator.onLine. Он не гарантирует доступность сервера; лучше ловить ошибку fetch.
  • Не обрабатывать навигационные запросы отдельно. Без этого при переходе на новую страницу офлайн покажет браузерную ошибку.

Итоги

  • Офлайн = кеш оболочки/ассетов + запасная офлайн-страница.
  • Оболочку кешируем на install, офлайн-страницу обязательно включаем в кеш.
  • В fetch для навигаций: сеть → кеш → офлайн-страница.
  • Надёжнее ловить ошибку fetch, чем доверять navigator.onLine.
Проверьте себя
1. Что обязательно нужно сделать с офлайн-страницей, чтобы она показывалась без сети?
AПодключить её в манифесте
BДобавить её в кеш (например, в список ASSETS на install)
CПоставить ей display: standalone
DЗарегистрировать как отдельный Service Worker
2. Почему не стоит полагаться только на navigator.onLine?
AОн работает только в Chrome
Btrue означает лишь наличие сетевого интерфейса, а не доступность сервера
CОн медленный
DОн недоступен в Service Worker