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

Допустим, вы уже знаете, что Service Worker (SW) превращает статический сайт в автономное приложение. Но на практике 90% студентов сталкиваются с одним и тем же: регистрация проходит, а кэш не работает, или обновление контента превращается в детектив. В этой статье мы развенчаем главные мифы, которые плодятся в серой зоне документации, и дадим конкретные шаги, как отлаживать SW на реальном проекте.
- Миф №1: «SW ловит все запросы». На самом деле Service Worker перехватывает только те запросы, которые попадают под его скоуп (scope). Если скрипт лежит в корне — работает всё. Если в /blog/sw.js — он видит только /blog/. Настройте навигацию через параметры регистрации или переместите файл.
- Миф №2: «После изменения кэша страница обновится сразу». Нет. После изменения файла sw.js браузер считает его другим только при малейшем изменении байт-кода. При этом старый воркер живёт до закрытия всех вкладок с сайтом. Используйте
self.skipWaiting()иclients.claim()для мгновенного захвата. - Миф №3: «Кэш работает сам, его не надо чистить». Это главная причина «висящего» старого контента. Если не удалять устаревшие ключи кэша (через
caches.delete()), память браузера раздувается. Проект реального клиента: добавили автоматическую чистку кэша старше 7 дней — скорость загрузки выросла на 40% из-за уменьшения размера хранилища. - Миф №4: «Сбой Service Worker видно сразу в вкладке Network». Ложь. Стандартная панель Network показывает запросы от документа, но SW работает отдельным процессом. Используйте вкладку Application (Chrome) → Service Workers. Там видно статус («activated», «waiting»), последнюю активность и ошибки.
- Миф №5: «Если регистрация выдала success — всё хорошо». Нет. Успешная регистрация говорит только о том, что браузер скачал скрипт. А вот если внутри SW есть ошибка (например, неправильный URL для кэша), событие
installне завершится, и воркер повиснет в состоянии «installing» навсегда. Решение:addEventListener('install', (event) => { event.waitUntil(/* проверьте все await */) }).
История из обучения. Студент Иван настраивает 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
Золотой стандарт отладки — панель Application → Service Workers. Здесь вы видите текущее состояние (installing, waiting, activate), а также можете принудительно перехватывать обновления (галочка «Update on reload»). Это даёт возможность каждый раз тестировать свежий SW, а не ждать 30 минут.
В этой же панели есть кнопка «Push» и «Sync» для проверки push-уведомлений и фоновой синхронизации без эмуляции сервера. Если ваш SW падает при попытке обработать push — увидите красную ошибку сразу, вместо лазания по файлам.
- Инструмент 1: Журнал сетевых запросов внутри SW. Chrome: Chrome DevTools → More Tools → Service Workers internals (chrome://serviceworker-internals/). Там видны все SW, их версии и логи.
- Инструмент 2: Cache Storage вручную. В панели Application → Cache → Cache Storage вы видите папки кэша. Удалите тестовые вручную, чтобы проверить, как сайт ведёт себя с пустым кэшем.
- Инструмент 3:
postMessage()между вкладкой и SW. Это «отладка по связи». На клиенте пишетеnavigator.serviceWorker.controller.postMessage('ping'), а в SW ловитеself.addEventListener('message', event => event.source.postMessage('pong')). Только так вы поймёте, живой ли воркер сейчас.
Как правильно управлять кэшем: стратегия 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
