Обновление SW и устаревший кеш
Урок разбирает самую болезненную проблему PWA: как обновить приложение и не застрять на старом кеше.
Устаревший кеш — ситуация, когда пользователь видит старую версию приложения, потому что Service Worker продолжает отдавать ресурсы из кеша предыдущей версии.
Почему кеш «застревает»
Главное удобство PWA — кеширование — оборачивается главной болью: вы выкатили новую версию, а пользователь по-прежнему видит старую. Причины две. Первая: ваш Service Worker по cache-first отдаёт старые файлы из кеша и не идёт в сеть. Вторая: новый Service Worker сам по умолчанию ждёт в состоянии waiting, пока открыты вкладки со старым. В результате обновление «не доезжает».
Версионирование кеша
Базовый приём — версия в имени кеша. При каждом релизе меняете версию, кешируете заново, а старый кеш чистите на activate:
const CACHE = 'app-v3'; // подняли версию при релизе
self.addEventListener('activate', function (event) {
event.waitUntil(
caches.keys().then(function (keys) {
return Promise.all(
keys.filter(function (k) { return k !== CACHE; })
.map(function (k) { return caches.delete(k); })
);
})
);
});Как браузер замечает новый SW
Браузер периодически и при перезагрузке скачивает файл sw.js и сравнивает его байт в байт со старым. Если есть отличие хоть на байт — это «новый» Service Worker, запускается его установка. Поэтому важно: сам файл sw.js не должен агрессивно кешироваться сервером, иначе браузер не увидит изменений. Обычно для него ставят короткий или нулевой срок кеширования.
релиз --> sw.js изменился --> браузер качает новый --> install
--> новый SW в waiting (старый ещё работает)
--> закрыли все вкладки ИЛИ skipWaiting --> activate --> чистка старых кешейskipWaiting и уведомление пользователя
Чтобы новая версия активировалась быстрее, используют self.skipWaiting(). Но активировать новый Service Worker «под пользователем» в момент работы рискованно (могут разъехаться версии ресурсов). Грамотный паттерн: новый Service Worker встаёт в waiting, приложение показывает плашку «Доступно обновление — обновить?», и только по клику пользователя посылает воркеру сообщение вызвать skipWaiting и перезагружает страницу.
// в SW
self.addEventListener('message', function (e) {
if (e.data === 'SKIP_WAITING') self.skipWaiting();
});
// на странице: при обнаружении waiting SW показать кнопку «Обновить»,
// по клику: reg.waiting.postMessage('SKIP_WAITING'); затем перезагрузкаЧастые ошибки
- Не менять имя кеша при релизе. Старые файлы живут вечно — обновление не видно.
- Кешировать sw.js на сервере надолго. Браузер не увидит новую версию воркера.
- Бездумный skipWaiting. Версии ресурсов могут разъехаться; лучше обновлять по согласию пользователя.
- Не чистить старые кеши. Память сайта раздувается от версии к версии.
Итоги
- Устаревший кеш — главная боль PWA: пользователь видит старую версию.
- Решение: версионировать имя кеша и чистить старые на
activate. - Файл
sw.jsне должен надолго кешироваться сервером. - Обновление лучше предлагать пользователю кнопкой, а не навязывать
skipWaiting.