Stale-While-Revalidate vs Cache First: яку стратегію кешування Service Worker обрати

Оновлено:
Stale-While-Revalidate vs Cache First: яку стратегію кешування Service Worker обрати

Коли Service Worker уже зареєстровано, головне питання змінюється: не «чи кешувати», а «як саме». Cache First віддає файл із кешу й не питає сервер, Stale-While-Revalidate віддає кеш і одразу оновлює його у фоні. Різниця здається дрібною, але від неї залежить, що побачить користувач після релізу, скільки трафіку витратить застосунок і чи не застрягне сайт на старій версії. Я вже розбирав, чому Cache First для HTML небезпечний, у статті «8 критичних помилок при інтеграції PWA». Тут розбираю сам вибір: що кожна стратегія робить, для яких ресурсів підходить і чим за це доводиться платити. Матеріал спирається на офіційну документацію Workbox, web.dev і WebKit.

⚡ Коротко: яка стратегія для чого

  • ✅ Cache First добре підходить для статики, особливо з хешем у назві файлу (CSS, JS, шрифти): за документацією Workbox він дає виграш у швидкості для незмінних файлів, бо пропускає перевірки свіжості на сервері.
  • ✅ Stale-While-Revalidate (SWR) підходить для ресурсів, які трохи важливо тримати свіжими, але не критично: Workbox наводить як приклад аватарки в соцмережі.
  • ⚠️ Ціна SWR: користувач бачить версію, яка на один візит старша, а застосунок завжди робить запит у мережу у фоні.
  • ⚠️ HTML зазвичай краще віддавати через Network First, а не Cache First.
Тип ресурсу З чого починати
HTML-сторінкиNetwork First
CSS і JS з хешем у назвіCache First
CSS і JS без хешуStale-While-Revalidate
Зображення, що змінюютьсяStale-While-Revalidate
ШрифтиCache First
Публічний GET APINetwork First або SWR
Авторизовані й персональні даніNetwork Only (без кешу)
Аудіо та відеоCache First з підтримкою Range-запитів

Це відправна точка, а не закон. Нижче пояснення, чому саме так і від чого залежить відхилення.

📚 Зміст статті

🧠 Як Service Worker кешує: Cache API, fetch і життєвий цикл

Стратегія кешування — це взаємодія між подією fetch у Service Worker і інтерфейсом Cache. Подія fetch перехоплює запит, а Cache API дає змогу зберігати й діставати відповіді вручну (caches.open, cache.put, cache.match, cache.delete).

Важливо не плутати його з HTTP-кешем. За документацією, Cache — окремий механізм, і налаштування Cache-Control не впливають на те, що потрапляє в нього. Тому у Service Worker ви керуєте кешем самі, а кожен ресурс, який ви туди поклали, залишається там, доки ви його не оновите чи не видалите.

Щодо життєвого циклу: новий Service Worker проходить встановлення й очікування, а активується, коли старий більше не контролює відкриті вкладки (докладніше на web.dev). Це впливає на оновлення, про що нижче. Для розпізнавання типу запиту Workbox радить використовувати request.destination, а не розширення файлу: так уникають помилок зіставлення за розширенням.

📦 Cache First: як працює і де ламається

Алгоритм за документацією Workbox: запит спершу йде в кеш; якщо відповідь там є, її віддають; якщо немає, звертаються до мережі, кладуть відповідь у кеш і повертають її користувачеві.

Плюси Мінуси
  • Миттєва відповідь при влученні в кеш, мережа не потрібна
  • Пропускає перевірки свіжості на сервері, які міг би робити HTTP-кеш
  • Ресурси доступні офлайн
  • Простий код і передбачувана поведінка
  • Закешований файл не оновлюється сам: його замінюють лише новою URL (хеш у назві), новою версією кешу чи оновленням воркера
  • Помилкова відповідь, що потрапила в кеш, залишається там і ламає сторінку надовго
  • Для HTML і даних, що часто змінюються, небезпечний (розбір у статті про 8 помилок PWA)

Коли обирати Cache First

  • Файли незмінні для своєї URL: збірка додає хеш у назву (app.3f9a1c.js), нова версія отримує нову адресу.
  • Важлива швидкість повторного візиту й робота офлайн.
  • Ресурс великий або рідко змінюється: шрифти, іконки, бібліотеки.

Коли не обирати

  • Назва файлу не змінюється між релізами (style.css), а вміст змінюється.
  • Це HTML, API-відповіді чи персональні дані.
  • Відповідь може бути непрозорою (opaque): без перевірки статусу в кеш може потрапити помилка (див. розділ про помилки).

🔄 Stale-While-Revalidate: як працює і яка ціна

Алгоритм за документацією Workbox: при першому запиті ресурс беруть із мережі й кладуть у кеш; при наступних віддають із кешу, а у фоні повторно запитують мережу й оновлюють запис; наступний запит уже отримає свіжішу версію. Якщо ресурсу в кеші немає, SWR чекає на мережу.

Плюси Мінуси
  • Швидка відповідь із кешу й регулярне оновлення запису
  • Документація називає його досить безпечною стратегією, бо вона регулярно оновлює записи
  • Помилка в кеші виправиться при наступному успішному запиті
  • Підходить для ресурсів, які варто тримати свіжими, але не критично
  • Користувач бачить версію, що на один візит старша за актуальну
  • Завжди робить мережевий запит у фоні: за описом Workbox, перевірка виконується незалежно від віку відповіді, тож трафіку витрачається більше
  • Складніший у ручній реалізації, ніж Cache First

Коли обирати SWR

  • Ресурс змінюється час від часу, але негайна свіжість не критична (зображення профілів, прев'ю, необов'язкові довідники).
  • Файл має сталу URL без хешу, і ви хочете, щоб він сам оновлювався.
  • Потрібна швидкість Cache First, але без ризику «застряглих» файлів.

Коли не обирати

  • Дані мають бути завжди точними (баланс, наявність, ціни в кошику): краще Network First або Network Only.
  • Трафік дорогий або обмежений, а ресурс великий і рідко змінюється: тоді Cache First заощадить мережу.

SWR без Service Worker: директива Cache-Control

Ідею SWR можна реалізувати й на рівні HTTP: web.dev описує заголовок Cache-Control: max-age=1, stale-while-revalidate=59, підтримка якого з'явилась у Chrome 75 і Firefox 68. Для вибору підходу web.dev радить орієнтуватися так: якщо у вас уже є Service Worker, зручніше його підхід; якщо не хочете розгортати й підтримувати воркер, достатньо заголовка. Якщо воркер уже обробляє запит, то першим відповідає він, а поведінка з заголовка спрацьовує, лише коли воркер не відповідає або сам викликає fetch().

⚖️ Порівняння Cache First і SWR

Критерій Cache First Stale-While-Revalidate
Швидкість повторного запиту Миттєво з кешу Миттєво з кешу (мережа у фоні)
Свіжість Не оновлюється, доки не зміниться URL або версія кешу Оновлюється при кожному запиті, але показується попередня версія
Витрати трафіку Мережа лише при відсутності в кеші Мережевий запит на кожне звернення
Офлайн Працює для закешованого Працює для закешованого (фонове оновлення мовчки не вдасться)
Складність Низька Середня
Головний ризик Застарілий файл «назавжди» Користувач бачить попередню версію; зайвий трафік

Інші стратегії коротко

Network First спершу йде в мережу, а за її відсутності віддає останню закешовану версію: за документацією, це хороший вибір для HTML і API, коли онлайн потрібна свіжа версія, а офлайн достатньо останньої доступної. Network Only завжди бере відповідь із мережі, гарантує свіжість, але не працює офлайн. Cache Only віддає лише закешоване, тож ресурси мають бути попередньо закешовані, і вони не оновлюються, доки не оновиться воркер. Чому Cache First для HTML небезпечний, розібрано в статті про 8 помилок PWA.

🗂️ Матриця вибору за типом ресурсу

Ресурс Рекомендація На що звернути увагу
HTML Network First (SWR лише якщо прийнятна застаріла версія й є сповіщення про оновлення) Cache First залишає користувача на старій версії; у Workbox є окрема інструкція про Navigation Preload для Network First HTML
CSS і JS з хешем Cache First Нова збірка дає нову URL, тож застарілого файлу не буде
CSS і JS без хешу SWR Cache First не оновить файл із тією самою URL (мій висновок із принципу роботи стратегії)
Зображення, що змінюються (аватарки) SWR Приклад із документації Workbox. Обмежте розмір кешу (ExpirationPlugin)
Незмінні зображення, шрифти Cache First Задайте ліміт записів і вік
Публічний GET API Network First або SWR Network First, якщо важлива актуальність; SWR, якщо швидкість важливіша за точність, плюс сповіщення про оновлення
Авторизовані сторінки, персональні дані, POST Network Only (не кешувати) Ризик показати чужі чи застарілі дані
Аудіо й відео Cache First + workbox-range-requests Медіа треба кешувати заздалегідь, а не на льоту (докладніше в розділі про помилки)
Ресурси чужих доменів (CDN) SWR або Network First Відповіді можуть бути непрозорими; Cache First для них ризикований (див. помилки)

Швидкий вибір за питаннями

  • Чи змінюється вміст за тієї самої URL? Ні → Cache First. Так → переходьте далі.
  • Чи критична актуальність у кожен момент? Так → Network First або Network Only. Ні → SWR.
  • Чи містить відповідь особисті дані? Так → не кешувати.
  • Чи потрібна робота офлайн? Так → Cache First, SWR або Network First (з фолбеком на кеш).

🛠️ Реалізація: чистий JS і Workbox

Cache First на чистому JS

const STATIC_CACHE = 'static-v1';

self.addEventListener('fetch', (event) => {
  const { request } = event;
  const dest = request.destination;

  if (request.method !== 'GET') return;
  if (dest !== 'style' && dest !== 'script' && dest !== 'font') return;

  event.respondWith((async () => {
    const cache = await caches.open(STATIC_CACHE);
    const cached = await cache.match(request);
    if (cached) return cached;

    const response = await fetch(request);
    // Кладемо в кеш лише успішні відповіді
    if (response.ok) {
      cache.put(request, response.clone());
    }
    return response;
  })());
});

Stale-While-Revalidate на чистому JS

const IMAGES_CACHE = 'images-v1';

self.addEventListener('fetch', (event) => {
  const { request } = event;
  if (request.method !== 'GET' || request.destination !== 'image') return;

  event.respondWith((async () => {
    const cache = await caches.open(IMAGES_CACHE);
    const cached = await cache.match(request);

    // Фоновий запит: оновлюємо кеш свіжою відповіддю
    const networkUpdate = fetch(request)
      .then((response) => {
        if (response.ok) cache.put(request, response.clone());
        return response;
      })
      .catch(() => undefined);

    // Не даємо воркеру завершитись, поки кеш оновлюється
    event.waitUntil(networkUpdate);

    return cached || (await networkUpdate) || Response.error();
  })());
});

Основа цих прикладів відповідає прикладам із документації Workbox. Від себе я додав перевірку response.ok і event.waitUntil. Приклади спрощені й не запускалися, тож перевірте їх на своєму проєкті.

Те саме на Workbox

import { registerRoute } from 'workbox-routing';
import { CacheFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';

// CSS і JS з хешем у назві: Cache First
registerRoute(
  ({ request }) => request.destination === 'script' || request.destination === 'style',
  new CacheFirst({
    cacheName: 'static-assets',
    plugins: [
      new ExpirationPlugin({ maxEntries: 60, purgeOnQuotaError: true }),
    ],
  })
);

// Зображення: Stale-While-Revalidate
registerRoute(
  ({ request }) => request.destination === 'image',
  new StaleWhileRevalidate({
    cacheName: 'images',
    plugins: [
      new CacheableResponsePlugin({ statuses: [200] }),
      new ExpirationPlugin({
        maxEntries: 100,
        maxAgeSeconds: 30 * 24 * 60 * 60,
        purgeOnQuotaError: true,
      }),
    ],
  })
);

Значення maxEntries і maxAgeSeconds у прикладі орієнтовні: підберіть їх під свій сайт. Окремі кеші для різних типів ресурсів (параметр cacheName) допомагають і з організацією, і з очищенням (документація Workbox).

🔁 Оновлення без сюрпризів

  • Версіонування. Для Cache First у розумній збірці файли отримують хеш у назві, а старі записи вичищаються разом із версією кешу. Приклад із skipWaiting(), clients.claim() і видаленням старих кешів дивіться в статті про 8 помилок PWA, а про оновлення воркера в Workbox: Handle updates immediately.
  • Повідомлення про нову версію для SWR. Оскільки SWR віддає попередню версію, користувачеві корисно показати, що є свіжіша. Workbox має для цього BroadcastUpdatePlugin: він надсилає сторінці повідомлення, коли закешована відповідь оновилась.
// sw.js
import { StaleWhileRevalidate } from 'workbox-strategies';
import { BroadcastUpdatePlugin } from 'workbox-broadcast-update';

new StaleWhileRevalidate({
  cacheName: 'api-public',
  plugins: [new BroadcastUpdatePlugin()],
});

// У сторінці
navigator.serviceWorker.addEventListener('message', async (event) => {
  if (event.data.meta === 'workbox-broadcast-update') {
    const { cacheName, updatedURL } = event.data.payload;
    // Покажіть користувачеві «Є нова версія. Оновити?»
  }
});

Формат повідомлення (type: 'CACHE_UPDATED', meta: 'workbox-broadcast-update', payload з cacheName та updatedURL) взято з документації модуля.

⚠️ Типові помилки

  • Кешування помилок і непрозорих відповідей. У Workbox відповіді без CORS (opaque) за замовчуванням обережно не кешуються, бо не видно статусу, і закешована помилка при Cache First залишиться надовго. Для таких відповідей документація радить Network First або SWR, а запити з HTML позначати атрибутом crossorigin, щоб отримати прозору відповідь. У власному коді завжди перевіряйте response.ok.
  • Необмежене зростання кешу. Використовуйте ExpirationPlugin. За документацією, maxEntries ефективніший за maxAgeSeconds для контролю обсягу, а purgeOnQuotaError дозволяє автоматично чистити кеш при нестачі місця. Непрозорі відповіді «важать» багато: у Chrome навіть невелика така відповідь зараховується приблизно як 7 МБ квоти.
  • Аудіо й відео. Потрібен workbox-range-requests, атрибут crossorigin в елементі <video> чи <audio>, а кешувати медіа слід заздалегідь (через precache чи cache.add()), бо під час відтворення завантажується лише частина, і кешування «на льоту» не спрацює.
  • Авторизовані сторінки й POST. Не кешуйте відповіді з особистими даними й не використовуйте Cache First для запитів, що змінюють стан. Приклад білого списку маршрутів є в статті про 8 помилок PWA.
  • Збій оновлення воркера. Якщо воркер зламав сайт, є інструкція Workbox із видалення проблемного Service Worker.

🍎 iOS і сховище

Квоти. За політикою сховища WebKit, починаючи з Safari 17 та iOS 17, для браузерного застосунку квота на один origin може сягати 60% від загального обсягу диска, а для інших застосунків із WebKit — 15%. Веб-застосунок із домашнього екрана має ті самі квоти, що й у браузері. Кеш вичищається за origin, коли загальне використання перевищує загальну квоту, а якщо origin запросив постійний режим через Storage API, його це не стосується. WebKit вирішує, чи задовольнити запит, за евристиками, наприклад, чи відкрито сайт як веб-застосунок із домашнього екрана.

Правило «7 днів». Часто пишуть, що Safari очищує кеш PWA через 7 днів без активності. WebKit уточнює: це стосується видалення даних, які записує скрипт, після семи днів використання Safari без взаємодії з сайтом, а веб-застосунки з домашнього екрана мають власний лічильник днів використання, що скидається при запуску застосунку. Тож для сайту, який відкривають лише у вкладках Safari, ризик втратити кеш реальний, а для встановленого PWA він значно менший. Усе одно не зберігайте в кеші єдину копію важливих даних: зберігайте їх на сервері, а кеш тримайте як прискорення.

Про інші умови iOS (домашній екран, push) дивіться в статті «PWA Push-сповіщення на iOS у 2026».

🔌 Кешування разом із push-сервісами

Якщо ви додаєте push-сервіс, у вас з'являється два завдання для одного Service Worker: кешування й обробка push. Документація кількох сервісів описує, як їх поєднати:

  • OneSignal дозволяє додати рядок importScripts до вашого воркера або розмістити його окремо за іншим шляхом чи областю, і прямо згадує, що воркер OneSignal із кореневою областю може конфліктувати з PWA.
  • FCM вимагає файл firebase-messaging-sw.js у корені домену.
  • Pushwoosh описує параметр serviceWorkerUrl і підключення свого воркера через importScripts.

Практичне правило з боку кешування: у вашому fetch-обробнику обробляйте лише запити, які ви справді кешуєте (GET свого origin з відомими типами ресурсів), і не перехоплюйте запити до самого push-сервісу. Порівняння сервісів і ризики конфлікту воркерів розібрано в статті «Push-сервіси для PWA».

📈 Як перевірити результат

  • DevTools → Application. Вкладки Service Workers і Cache Storage показують стан воркера й вміст кешів (так це описує документація Workbox).
  • Network. Дивіться, які запити віддає воркер, а які йдуть у мережу. У SWR при кожному зверненні має бути фоновий запит.
  • Офлайн-режим. Увімкніть імітацію офлайн і перезавантажте сторінку: хоча б оболонка й критичні ресурси мають відкритись.
  • Оновлення після релізу. Задеплойте нову збірку й переконайтеся, що користувач (у тому числі у вже відкритій вкладці) отримує нову версію за очікуваний час.
  • Lighthouse. Допомагає перевірити загальну картину PWA й продуктивності.

❓ Часті питання (FAQ)

Що обрати для CSS і JS: Cache First чи SWR?

Якщо файли мають хеш у назві, зазвичай підходить Cache First: нова збірка дає нову URL. Якщо назва не змінюється між релізами, використовуйте SWR або Network First, інакше користувач може залишитися зі старим файлом.

Чи можна кешувати HTML через SWR?

Технічно так, але користувач бачитиме версію, що на один візит старша. Якщо це прийнятно й ви показуєте сповіщення про оновлення, SWR працює. Для сторінок, де важлива актуальність, ліпше Network First.

Чи потрібен Workbox?

Ні, усе можна написати вручну, як у прикладах вище. Workbox пропонує готові стратегії та плагіни (ExpirationPlugin, BroadcastUpdatePlugin, RangeRequestsPlugin), що зменшує кількість помилок. Сторінки документації Workbox, на які я посилаюсь, востаннє оновлювались у 2018–2021 роках, тож перед використанням звіряйте версії.

Чим SWR у Service Worker відрізняється від stale-while-revalidate у Cache-Control?

Ідея та сама, але рівень різний: заголовок працює на рівні HTTP-кешу браузера й не потребує воркера, а SWR у воркері керується вашим кодом (або Workbox) і може працювати з Cache API. Якщо воркер вже відповідає на запит, першим спрацьовує він.

Чому користувач після релізу бачить стару версію?

Типові причини: HTML віддається через Cache First, назва файлу не змінилась при Cache First, новий воркер чекає на закриття вкладок. Перевірте стратегію для кожного типу ресурсу й життєвий цикл воркера.

✅ Висновки

  • 🔹 Вибір стратегії робить не «одна найкраща», а відповідність типу ресурсу: незмінні файли з хешем добре чують Cache First, а ресурси, які варто оновлювати без зайвого клопоту, підходять для SWR.
  • 🔹 SWR платить за свіжість двома речами: користувач бачить попередню версію, а мережа отримує додатковий запит.
  • 🔹 HTML і дані, що швидко змінюються, краще віддавати через Network First, персональні відповіді не кешувати.
  • 🔹 Завжди перевіряйте статус відповіді перед записом у кеш і обмежуйте розмір кешу.
  • 🔹 На iOS враховуйте квоти й різницю між вкладкою Safari та встановленим PWA.
  • 🔹 Якщо ви додаєте push-сервіс, заздалегідь продумайте, як поєднати його воркер із вашим.

Загальне порівняння PWA і нативного застосунку, якщо ви обираєте платформу, дивіться тут: PWA vs Native у 2026.

📎 Джерела

Важливо: документація, на яку я спираюсь, частково датована 2018–2021 роками (сторінки Workbox), а політики браузерів змінюються. Перед використанням перевіряйте актуальні версії бібліотек і поведінку браузерів. Матеріал актуальний на жовтень 2026 року.