Создание и отладка Service Worker

t

Допустим, вы уже знаете, что Service Worker (SW) превращает статический сайт в автономное приложение. Но на практике 90% студентов сталкиваются с одним и тем же: регистрация проходит, а кэш не работает, или обновление контента превращается в детектив. В этой статье мы развенчаем главные мифы, которые плодятся в серой зоне документации, и дадим конкретные шаги, как отлаживать SW на реальном проекте.

История из обучения. Студент Иван настраивает SW для фотоблога на курсе «Продвинутая PWA». Первый запуск — кэширует 15 изображений по 2 МБ. Через три дня пользователи жалуются, что старые фото всё ещё видны. Иван ищет ошибку в fetch, проверяет заголовки — всё ок. А проблема оказалась в логике обновления: он забыл удалить старый ключ кэша ('v1'), а новый ключ ('v2') создавался, но браузер использовал одновременно оба. Пришлось добавить caches.keys().then(names => names.forEach(name => if (name !== CACHE_NAME) caches.delete(name))). Результат: автономный блог обновляется за 2 секунды, а не за неделю ожидания.

Как правильно зарегистрировать Service Worker: три типовые ошибки

Первая ошибка — регистрация в неподходящий момент. Никогда не вызывайте register() до загрузки DOM. Правильный паттерн: window.addEventListener('load', async () => { try { const reg = await navigator.serviceWorker.register('/sw.js', { scope: '/' }); console.log('Регистрация успешна:', reg.scope); } catch(e) { console.error('Регистрация провалилась:', e); } });. Так вы не блокируете отрисовку initial paint.

Вторая ошибка — игнорирование флага updateViaCache. По умолчанию браузер кэширует sw.js через HTTP-кэш. Без updateViaCache: 'none' вы можете править скрипт, а браузер всё равно возьмёт старую версию из кэша. Установите его явно.

Третья ошибка — отсутствие проверки поддержки. SW не работает в Safari до последних версий. Всегда делайте if ('serviceWorker' in navigator) { /* регистрация */ }. Если не проверить — в старых браузерах упадёт вся логика и пользователь увидит белую страницу.

Отладка Service Worker в DevTools: что смотреть кроме вкладки Console

Золотой стандарт отладки — панель ApplicationService Workers. Здесь вы видите текущее состояние (installing, waiting, activate), а также можете принудительно перехватывать обновления (галочка «Update on reload»). Это даёт возможность каждый раз тестировать свежий SW, а не ждать 30 минут.

В этой же панели есть кнопка «Push» и «Sync» для проверки push-уведомлений и фоновой синхронизации без эмуляции сервера. Если ваш SW падает при попытке обработать push — увидите красную ошибку сразу, вместо лазания по файлам.

Как правильно управлять кэшем: стратегия stale-while-revalidate с умной чисткой

Многие используют стратегию Cache First, Network Fallback для всех ресурсов. Это ошибка: если контент на сервере меняется каждый день, пользователь получит старую версию до следующей перезагрузки во второй вкладке. Используйте Stale-While-Revalidate: ответ из кэша приходит мгновенно, а асинхронно отправляется fetch и обновляет кэш для следующего визита.

Реализация: в событии fetch (self.addEventListener('fetch', (event) => { event.respondWith(caches.open(CACHE).then(cache => cache.match(event.request).then(cachedResponse => { const fetchPromise = fetch(event.request).then(networkResponse => { if (networkResponse.ok) caches.open(CACHE).then(c => c.put(event.request, networkResponse.clone())); return networkResponse; }); return cachedResponse || fetchPromise; }))); });). Это даёт скорость offline-first, но свежие данные на следующем запросе.

Теперь о чистке. Не ждите, пока браузер сам всё удалит — он вообще не удаляет. Алгоритм: при активации (activate) переберите все кэши через caches.keys(). Удалите всё, что не совпадает с вашим CACHE_NAME. Например: event.waitUntil(caches.keys().then(keys => Promise.all(keys.filter(key => key !== CACHE_NAME).map(key => caches.delete(key)))).then(() => clients.claim()));. Это освобождает до 50% storage.

Конкретный кейс из курса «PWA для eCommerce»: скрипт SW кэшировал не только HTML, но и AJAX-запросы с фильтрами товаров. После каждой смены фильтра кэш не чистился — копил до 12 МБ мусора в день. Внедрили max-age вручную через Date.now() внутри fetch, и задали лимит в 5 МБ. Через месяц storage reduction — 78%.

Почему не обновляется Service Worker: скрытые механизмы браузера

Типичная ситуация: вы изменили файл sw.js, загрузили его на сервер, а старая версия всё ещё активна. Браузер проверяет обновления SW только при навигации (переходе по страницам) или через 24 часа (если это fetch с navigate). То есть если вы обновили файл, пользователь должен уйти на другую страницу (или принудительно перезагрузить вкладку), чтобы произошло скачивание новой версии.

Чтобы форсировать проверку, используйте navigator.serviceWorker.register() с тем же URL каждый раз. При каждом вызове (например, на каждой странице) браузер будет делать HEAD-запрос к sw.js и сравнивать Last-Modified или ETag. Если заголовки не совпадают — скачает новую версию. Обязательно: updateViaCache: 'none'.

Ещё один миф: «чтобы обновить SW, нужно закрыть все вкладки». Да, старый воркер жив до закрытия всех клиентов. Но мы уже обсудили clients.claim() — он заставляет текущий SW взять управление сразу, даже если есть предыдущие. Вызывайте его в момент активации, и проблема исчезнет.

Напоследок, не забывайте про self.skipWaiting() внутри события install. Это заставляет новую версию ждать максимум 0 миллисекунд. Пример: self.addEventListener('install', (event) => { event.waitUntil(self.skipWaiting()); }). Так вы избегаете два одновременных версии SW, работающих параллельно.

Сценарии, где SW ломает UX, и как это исправить

Первый сценарий — зависший файл в кэше. Пользователь загрузил страницу в офлайн — получил испорченный CSS (URL изменился). Решение: всегда делать fallback в fetch: если ресурс не получен, вернуть caches.match(fallbackUrl) или даже специальный 404-офлайн-пейдж.

Второй сценарий — накопление устаревших cookie после fetch, отправленных не через cache. Если SW кэширует POST-запросы (категорически не рекомендуется), это ломает сессию. Решение: в fetch проверять event.request.method — кэшировать только GET.

Третий сценарий — долгое отключение интернета. Если кэш пуст (первый заход в офлайн), пользователь получает глобальное сообщение «нет подключения». Решение: в событие fetch добавить caches.match(event.request).then(response => response || fetch(event.request).catch(() => new Response('Offline', { status: 503 }))).

Пример из клиентской практики тренировочного приложения по доставке: из-за неправильного scope SW не перехватывал динамические страницы ( /order/12345 ), и при выключении интернета кешировались только корневая страница. Исправление: переместили sw.js в корень и установили scope: '/ '. После этого все подстраницы стали работать автономно.

Заключение: шаги для стабильного Service Worker

Главный вывод — не доверяйте первому успешному запуску. Мифы ломают UX, а из-за ложного ощущения «всё работает» студенты теряют время и трафик. Проверяйте три вещи после каждого деплоя: 1) активирован ли новый SW (DevTools → Application → Service Workers); 2) обновился ли кэш (ключи должны поменяться); 3) падает ли fetch при offline (отключите Wi‑Fi и пройдитесь по страницам).

Если следовать описанному алгоритму — стабильная работа SW на реальном проекте. Вы получите реальное ускорение загрузки и автономность без странных глитчей. Тестируйте на реальных девайсах с разной скоростью интернета, а не только на Desktop. И помните: курс «Платформа для обучения веб-разработке и дизайну» специально наполнен реальными кейсами с SW, так что вы минимизируете время на отладку в 5-8 раз по сравнению с чтением сырой документации.

Добавлено: 23.04.2026