Ви додали manifest.json, зареєстрували Service Worker, протестували — все працює.
А через тиждень користувачі бачать стару версію сайту, Google індексує неправильні сторінки,
а аналітика показує вдвічі менше переглядів, ніж насправді.
Спойлер: 90% цих проблем — наслідок типових помилок при інтеграції PWA, які легко уникнути, якщо знати про них заздалегідь.
⚡ Коротко
- ✅ Кешування HTML — найнебезпечніша помилка: користувачі бачать застарілий контент, а ви навіть не знаєте про це
- ✅ Service Worker може заблокувати оновлення: без правильної стратегії активації старий код живе вічно
- ✅ PWA на iOS має унікальні обмеження: Safari очищує кеш через 7 днів неактивності
- 🎯 Ви отримаєте: 8 детальних розборів з форматом "Сценарій → Проблема → Рішення" та готовим кодом для кожної ситуації
- 👇 Нижче — реальні приклади, фрагменти коду та чек-лист перевірки
📚 Зміст статті
🎯 Помилка 1. Кешування HTML-сторінок (найнебезпечніша помилка)
Кешування динамічного HTML
Якщо Service Worker кешує HTML-сторінки за стратегією Cache First, користувачі назавжди залишаються
на старій версії сайту. Нові товари, акції, виправлення багів — нічого з цього вони не побачать,
поки вручну не очистять кеш браузера.
Кешувати CSS та JS — правильно. Кешувати HTML із Cache First — шлях до невидимих оновлень.
📋 Сценарій
Ви розгорнули PWA для інтернет-магазину. Service Worker кешує всі відповіді, включаючи HTML-сторінки,
за стратегією Cache First — спочатку дивиться в кеш, і тільки якщо там немає — йде в мережу.
Через тиждень ви оновили каталог товарів, змінили ціни та додали банер з акцією. Але користувачі,
які вже відвідували сайт раніше, продовжують бачити стару версію.
❌ Проблема
Стратегія Cache First для HTML означає: браузер навіть не намагається перевірити, чи є новіша
версія на сервері. Він завжди віддає те, що вже є в кеші. Для статичних ресурсів (шрифти,
іконки, бібліотеки) це ідеально. Для HTML, який змінюється — це катастрофа.
Користувач не бачить оновлень, не бачить нових товарів, може навіть оформити замовлення
на товар, якого вже немає в наявності.
✅ Правильна стратегія
Використовуйте Network First для HTML-сторінок: спочатку запит йде на сервер,
і тільки якщо мережа недоступна — показується кешована версія. Так користувач завжди отримує
свіжий контент, а кеш слугує лише як резервний варіант для офлайн-режиму.
self.addEventListener('fetch', event => {const url = new URL(event.request.url);
// Статичні ресурси — Cache First (змінюються рідко)
if (url.pathname.match(/\.(css|js|png|jpg|woff2)$/)) {
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetch(event.request))
);
return;
}
// HTML-сторінки — Network First (завжди свіжий контент)
event.respondWith(
fetch(event.request)
.then(response => {
if (response.ok) {
const clone = response.clone();
caches.open('html-cache').then(cache =>
cache.put(event.request, clone)
);
}
return response;
})
.catch(() => caches.match(event.request))
);
});
Ключовий принцип: розділяйте кеш на два типи. Статика (CSS, JS, зображення) —
Cache First з версіонуванням через ім'я кешу. HTML та API — Network First, щоб дані завжди
були актуальними.
🎯 Service Worker блокує оновлення сторінок
Застарілий Service Worker
Без skipWaiting() та правильної стратегії активації новий Service Worker чекає,
поки користувач закриє всі вкладки сайту. Це може тривати дні або тижні — весь цей час
працює стара версія з потенційними багами.
Ви задеплоїли фікс критичного бага, але користувачі його не отримують — бо старий Service Worker досі "на варті".
📋 Сценарій
Ви знайшли та виправили серйозний баг у кешуванні — деяким користувачам показувалась чужа
сторінка профілю. Задеплоїли новий sw.js з виправленням. Але нова версія
Service Worker не активується — вона чекає в статусі "waiting", поки всі вкладки з вашим
сайтом не будуть закриті. Користувачі, які тримають вкладку відкритою постійно, можуть
не отримати оновлення тижнями.
❌ Проблема
За замовчуванням браузер не активує новий Service Worker, поки старий обслуговує хоча б одну
відкриту вкладку. Це зроблено для стабільності — щоб під час сесії користувача логіка кешування
не змінилася раптово. Але на практиці це означає, що критичні оновлення застрягають у черзі.
На мобільних пристроях ситуація ще гірша — Safari на iOS може тримати PWA "живою" у фоні
протягом тривалого часу.
✅ Правильна стратегія
Додайте skipWaiting() у подію install — це змушує новий Service Worker
негайно стати активним, не чекаючи закриття вкладок. А clients.claim() у подію
activate гарантує, що вже відкриті сторінки одразу перейдуть під контроль нового SW.
Додатково — версіонуйте кеш, щоб старі дані видалялися автоматично.
const CACHE_NAME = 'my-app-v3'; // Змінюйте версію при кожному деплоїself.addEventListener('install', event => {
// Не чекаємо закриття вкладок — активуємось негайно
self.skipWaiting();
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(STATIC_ASSETS))
);
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
// Видаляємо всі старі версії кешу
keys.filter(key => key !== CACHE_NAME)
.map(key => caches.delete(key))
)
).then(() => {
// Беремо під контроль усі відкриті вкладки
return self.clients.claim();
})
);
});
Порада: автоматизуйте зміну версії кешу. Найпростіший варіант — використовуйте
timestamp або хеш git-коміту як частину імені кешу. Так ви ніколи не забудете оновити версію
після деплою.
🎯 PWA перехоплює навігацію (помилка роутингу)
Service Worker перехоплює чужі маршрути
Якщо Service Worker зареєстрований у кореневій директорії без фільтрації маршрутів,
він перехоплює запити до адмін-панелі, API-ендпоінтів, webhook-ів та сторонніх сервісів.
Це призводить до помилок авторизації, некоректних відповідей та "зависання" інтерфейсу.
Адміністратор не може увійти в адмін-панель, бо Service Worker віддає йому кешовану головну сторінку замість /admin/login.
📋 Сценарій
У вас є сайт з клієнтською частиною на / та адмін-панеллю на /admin.
Service Worker зареєстрований з scope: "/" і кешує всі GET-запити. Адміністратор
переходить на /admin/dashboard, але замість адмін-панелі бачить кешовану версію
головної сторінки. Або ще гірше — API-запити до /api/webhook від платіжної системи
перехоплюються SW і повертають HTML замість JSON.
❌ Проблема
Service Worker за замовчуванням перехоплює всі запити в межах свого scope. Якщо scope — це корінь
сайту /, він обробляє абсолютно все: клієнтські сторінки, адмін-панель, REST API,
webhook-и, файли завантаження. Без явної фільтрації маршрутів це призводить до непередбачуваної
поведінки — особливо для авторизованих сторінок, де кешування може показати дані іншого користувача.
✅ Правильна стратегія
Чітко визначте у fetch-обробнику, які маршрути має обробляти Service Worker,
а які — пропускати без втручання. Правило просте: все, що не є публічним клієнтським контентом,
повинно проходити повз SW напряму до сервера.
self.addEventListener('fetch', event => {const url = new URL(event.request.url);
// Пропускаємо все, що не потребує кешування
if (event.request.method !== 'GET') return;
if (url.pathname.startsWith('/api/')) return;
if (url.pathname.startsWith('/admin')) return;
if (url.pathname.startsWith('/webhook')) return;
if (url.pathname.startsWith('/login')) return;
if (url.pathname.startsWith('/register')) return;
if (url.pathname.startsWith('/logout')) return;
// Авторизовані сторінки — не кешуємо
if (url.pathname.startsWith('/dashboard')) return;
if (url.pathname.startsWith('/profile')) return;
if (url.pathname.startsWith('/payment')) return;
// Аудіо та великі файли — не кешуємо
if (url.pathname.startsWith('/audio')) return;
if (url.pathname.startsWith('/upload')) return;
// Тільки публічний контент — Network First
event.respondWith(
fetch(event.request)
.then(response => {
if (response.ok) {
const clone = response.clone();
caches.open('public-cache').then(c => c.put(event.request, clone));
}
return response;
})
.catch(() => caches.match(event.request))
);
});
Принцип "білого списку": замість того, щоб думати "що виключити", краще визначити
"що саме кешувати". Публічні сторінки (головна, блог, каталог, FAQ) — кешуємо. Все інше — пропускаємо.
Це безпечніше, ніж намагатися передбачити всі маршрути, які не можна кешувати.
🎯 SPA-навігація без pageview подій
Аналітика втрачає дані
У Single Page Application (SPA) переходи між сторінками відбуваються без перезавантаження.
Google Analytics та інші системи аналітики фіксують тільки перше завантаження, а всі наступні
переходи — невидимі. Ви втрачаєте 60-80% реальних даних про поведінку користувачів.
За аналітикою у вас 1000 переглядів на день. Насправді — 5000. Але ви цього не знаєте, бо SPA "з'їдає" pageview.
📋 Сценарій
Ви запустили PWA як SPA на React або Vue. Користувач заходить на головну сторінку — Google Analytics
фіксує pageview. Далі він переходить на каталог, відкриває товар, читає відгуки — але все це
відбувається без перезавантаження сторінки (client-side routing). GA не фіксує жодного з цих
переходів. У вашому звіті — 1 перегляд замість 4.
❌ Проблема
Класичний Google Analytics (і більшість аналітичних систем) фіксує pageview при завантаженні
сторінки — коли браузер виконує повний запит до сервера. У SPA цього не відбувається: JavaScript
змінює URL через History API та підставляє новий контент без перезавантаження.
Наслідки: некоректна воронка конверсії, заниження популярних сторінок, неможливість оцінити
ефективність контенту та рекламних кампаній.
✅ Правильна стратегія
Відстежуйте зміни URL через History API та вручну відправляйте pageview при кожному переході.
Для Google Analytics 4 (GA4) використовуйте подію page_view. Для серверних додатків
на Thymeleaf / Spring Boot, де кожна сторінка — це повний запит, ця проблема менш актуальна,
але якщо ви використовуєте AJAX-навігацію або htmx — перевірте.
// Для SPA: відстежуємо кожну зміну маршруту// React Router, Vue Router або ручна навігація
function trackPageView(path) {
gtag('event', 'page_view', {
page_path: path,
page_title: document.title
});
}
// Варіант 1: слухаємо popstate (кнопки "назад/вперед")
window.addEventListener('popstate', () => {
trackPageView(window.location.pathname);
});
// Варіант 2: перехоплюємо pushState
const originalPushState = history.pushState;
history.pushState = function() {
originalPushState.apply(this, arguments);
trackPageView(arguments[2]); // третій аргумент — URL
};
// Варіант 3: для React Router
// у кореневому компоненті
useEffect(() => {
trackPageView(location.pathname);
}, [location.pathname]);
Важливо: якщо ваш PWA побудований на серверному рендерингу (Thymeleaf, Django,
Laravel) і кожна сторінка — це окремий HTTP-запит, ця помилка вас не стосується. GA працюватиме
коректно "з коробки". Проблема актуальна саме для SPA-фреймворків (React, Vue, Angular).
🎯 Проблеми з canonical URL
Дублювання сторінок в індексі Google
PWA може створити кілька версій однієї сторінки: ?source=pwa,
?utm_source=homescreen, з trailing slash та без. Google індексує їх як різні
сторінки, розмиваючи SEO-вагу та створюючи дублі в пошуковій видачі.
Одна сторінка — три URL в індексі Google. Ваш SEO-трафік ділиться на три замість того, щоб концентруватися на одному.
📋 Сценарій
У manifest.json ви вказали "start_url": "/?source=pwa", щоб
відстежувати в аналітиці, скільки людей заходять через встановлений PWA. Також маркетолог
додав UTM-мітки для рекламних кампаній. В результаті одна й та сама головна сторінка доступна
за трьома адресами: /, /?source=pwa і
/?utm_source=homescreen. Google бачить три різні сторінки з однаковим контентом.
❌ Проблема
Пошукові роботи індексують URL повністю — з усіма параметрами. Без правильного canonical-тегу
Google не знає, яку версію вважати основною. Він може обрати будь-яку з них — або навіть
вважати їх дублями та знизити рейтинг усіх трьох. Це класична проблема "розмивання" SEO-ваги.
Особливо болюче для сайтів, де кожна сторінка — це потенційна точка входу з пошуку.
✅ Правильна стратегія
Завжди вказуйте canonical URL без параметрів відстеження. У manifest.json можна
залишити start_url з параметром для аналітики, але на самій сторінці canonical
повинен вказувати на чисту URL. Також переконайтеся, що canonical однаковий для версій з
trailing slash та без нього.
<!-- На КОЖНІЙ сторінці сайту --><link rel="canonical" href="https://example.com/catalog">
<!-- Навіть якщо реальна URL: -->
<!-- https://example.com/catalog?source=pwa -->
<!-- https://example.com/catalog?utm_source=homescreen -->
<!-- https://example.com/catalog/ (з trailing slash) -->
<!-- manifest.json — тут параметр допустимий для аналітики -->
{
"start_url": "/?source=pwa"
}
<!-- Thymeleaf приклад -->
<link rel="canonical" th:href="${pageSEO.canonicalUrl}">
<!-- Контролер Spring Boot -->
// Canonical завжди без параметрів
String canonical = "https://kazkiua.com" + request.getRequestURI();
seo.setCanonicalUrl(canonical); // без ?source=pwa
Перевірка: відкрийте Google Search Console → Сторінки → Виключені →
"Дублікат без канонічного тегу". Якщо бачите там свої сторінки з параметрами — canonical
налаштований неправильно.
🎯 HTTPS забули на піддомені
Service Worker не реєструється без HTTPS
Service Worker — ключовий компонент PWA — працює виключно через HTTPS (єдиний виняток —
localhost для розробки). Якщо ваш піддомен (api.example.com,
cdn.example.com) працює через HTTP, SW не зможе кешувати ресурси з цього джерела.
Основний домен на HTTPS, а CDN для зображень — на HTTP. Service Worker мовчки ігнорує всі картинки.
📋 Сценарій
Ваш сайт https://example.com працює через HTTPS. Service Worker зареєстрований
та кешує сторінки. Але зображення завантажуються з CDN на http://cdn.example.com
(без SSL). При спробі кешувати ці зображення SW отримує помилку mixed content — браузер блокує
HTTP-запити зі сторінки, завантаженої через HTTPS. В результаті при офлайн-режимі всі
зображення зникають.
❌ Проблема
Вимога HTTPS поширюється не тільки на основний домен, а й на всі ресурси, які завантажуються
на сторінці. Mixed content (змішування HTTPS та HTTP) блокується сучасними браузерами.
Service Worker навіть не зможе виконати fetch() до HTTP-ресурсу зі сторінки на HTTPS.
Ця помилка особливо підступна, бо все працює при активному інтернеті (браузер завантажує
зображення напряму), але ламається в офлайн-режимі (SW не може віддати некешоване).
✅ Правильна стратегія
Переконайтеся, що абсолютно всі ресурси — основний домен, піддомени, CDN, зовнішні API —
працюють через HTTPS. Використовуйте безкоштовні сертифікати Let's Encrypt. Для CDN-сервісів
(Cloudinary, AWS CloudFront, Cloudflare) HTTPS зазвичай увімкнений за замовчуванням — перевірте це.
<!-- ❌ НЕПРАВИЛЬНО: mixed content --><img src="http://cdn.example.com/photo.jpg">
<script src="http://analytics.example.com/tracker.js"></script>
<!-- ✅ ПРАВИЛЬНО: все через HTTPS -->
<img src="https://cdn.example.com/photo.jpg">
<script src="https://analytics.example.com/tracker.js"></script>
<!-- ✅ АБО: protocol-relative URL (використовує протокол сторінки) -->
<img src="//cdn.example.com/photo.jpg">
<!-- Перевірка в DevTools: Console → фільтр "Mixed Content" -->
<!-- Або: Security tab → перевірте статус кожного ресурсу -->
Швидка перевірка: відкрийте DevTools → Console та шукайте попередження
"Mixed Content". Або перейдіть на вкладку Security — вона покаже, які ресурси завантажуються
через небезпечне з'єднання.
🎯 manifest.json без правильного scope
PWA "захоплює" чужі URL
Параметр scope у manifest.json визначає, які URL належать вашому PWA. Без нього
або з занадто широким scope PWA може перехоплювати навігацію на сторонні сервіси,
платіжні шлюзи та OAuth-сторінки, ламаючи критичні потоки.
Користувач натискає "Оплатити" — і замість переходу на платіжну сторінку бачить помилку, бо PWA намагається відкрити її у своєму вікні.
📋 Сценарій
Ваш PWA встановлений на домашній екран. Користувач оформляє замовлення та натискає "Оплатити".
Сайт перенаправляє на https://pay.example.com/checkout — зовнішній платіжний шлюз.
Але PWA відкриває цю URL у своєму standalone-вікні замість зовнішнього браузера. Платіжна сторінка
не працює коректно без повного браузерного середовища, і оплата зривається.
❌ Проблема
Якщо scope не вказаний у manifest.json, браузер визначає його автоматично на основі
розташування маніфесту. Це може призвести до того, що PWA вважає своїми URL, які до нього
не належать. Зовнішні посилання, які повинні відкриватися у браузері (платежі, OAuth, соціальні
мережі), відкриваються у вікні PWA — і часто ламаються.
✅ Правильна стратегія
Явно вкажіть scope у manifest.json. Для більшості сайтів це "/" — корінь
вашого домену. URL за межами scope автоматично відкриватимуться у зовнішньому браузері.
Для зовнішніх посилань у вашому HTML додавайте target="_blank".
// manifest.json{
"name": "My PWA",
"short_name": "MyPWA",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#4f46e5"
}
// Якщо PWA живе у підкаталозі:
{
"start_url": "/app/",
"scope": "/app/"
}
// У HTML — зовнішні посилання відкриваємо у браузері
<a href="https://pay.example.com/checkout"
target="_blank"
rel="noopener noreferrer">
Оплатити
</a>
<a href="https://accounts.google.com/oauth"
target="_blank"
rel="noopener noreferrer">
Увійти через Google
</a>
Правило: все, що виходить за межі вашого домену — платежі, OAuth, соціальні
мережі — повинно мати target="_blank". Це гарантує відкриття у повному браузері
незалежно від налаштувань scope.
🎯 iOS Safari очищує кеш через 7 днів
Apple видаляє дані PWA при неактивності
Safari на iOS автоматично очищує весь кеш PWA (Cache API, IndexedDB, LocalStorage), якщо
користувач не відкривав додаток протягом 7 днів. Це означає, що офлайн-функціональність,
збережені дані та навіть авторизація — все зникає без попередження.
Користувач відкриває PWA через тиждень відпустки — і починає все з нуля: авторизація скинута, збережені дані зникли, кеш порожній.
📋 Сценарій
Ви створили PWA для читання статей офлайн. Користувач зберіг 20 статей у кеш, щоб читати
в літаку. Але рейс перенесли на 10 днів. Коли він нарешті відкриває PWA — кеш порожній.
Safari видалив усі збережені дані, бо PWA не використовувався більше 7 днів.
Жодного попередження — просто порожній екран.
❌ Проблема
Apple впровадила політику Intelligent Tracking Prevention (ITP), яка обмежує зберігання
даних для веб-сайтів. Для PWA на iOS це означає: якщо користувач не відвідує додаток протягом
7 днів, Safari має право видалити всі дані, записані через JavaScript — Cache API, IndexedDB,
LocalStorage, SessionStorage. Це обмеження не стосується нативних додатків з App Store,
тільки PWA. На Android такого обмеження немає.
✅ Правильна стратегія
Повністю обійти це обмеження неможливо — це рішення Apple на рівні операційної системи.
Але можна мінімізувати наслідки: не покладайтеся на клієнтський кеш як єдине сховище даних,
зберігайте критичні дані на сервері, і при відкритті PWA завжди перевіряйте наявність кешу
та відновлюйте його за потреби.
// При кожному запуску PWA — перевіряємо стан кешуasync function checkAndRestoreCache() {
const cache = await caches.open('my-app-v3');
const keys = await cache.keys();
if (keys.length === 0) {
console.log('Кеш порожній — відновлюємо базові ресурси');
await cache.addAll(STATIC_ASSETS);
// Якщо є авторизація — перевіряємо токен
const token = localStorage.getItem('auth_token');
if (!token) {
// Токен теж видалений — перенаправляємо на логін
window.location.href = '/login';
}
}
}
// Викликаємо при завантаженні
if ('serviceWorker' in navigator) {
checkAndRestoreCache();
}
// Порада: зберігайте критичні дані на сервері
// Клієнтський кеш — це прискорення, а не сховище
// ❌ Не зберігайте тут: чернетки, налаштування, прогрес
// ✅ Зберігайте на сервері, а кеш — лише для швидкості
Головне правило для iOS: клієнтський кеш — це оптимізація швидкості, а не
надійне сховище. Все важливе повинно зберігатися на сервері. Кеш може зникнути будь-якого
моменту — і ваш додаток повинен бути готовий до цього.
❓ Часті питання (FAQ)
Чи потрібен Service Worker, якщо мій сайт — серверний (Thymeleaf, Django, Laravel)?
Так, якщо ви хочете PWA-функціональність: встановлення на домашній екран, офлайн-режим,
швидке завантаження. Але стратегія кешування буде простішою — Network First для HTML,
Cache First для статики. SPA-специфічні проблеми (помилка 4) вас не стосуються.
Як перевірити, чи правильно працює мій Service Worker?
Chrome DevTools → Application → Service Workers. Тут ви побачите статус SW (active, waiting,
redundant), зможете примусово оновити або зупинити його. Вкладка Cache Storage покаже,
що саме закешовано. Також запустіть Lighthouse → PWA — він покаже список проблем.
Чи можна відкликати (видалити) Service Worker, якщо він зламав сайт?
Так. Задеплойте новий sw.js з порожнім fetch-обробником, або використайте
self.registration.unregister(). У крайньому випадку — користувач може вручну
видалити SW через DevTools → Application → Service Workers → Unregister.
Чи впливає PWA на SEO?
Позитивно — якщо правильно налаштований. PWA покращує Core Web Vitals (швидкість завантаження),
що є фактором ранжування Google. Але неправильне кешування HTML (помилка 1) або дублі
canonical (помилка 5) можуть нашкодити SEO.
Обмеження iOS — це назавжди?
Ситуація повільно покращується. Apple додала push-повідомлення в iOS 16.4, background sync
в Safari 18+. Але 7-денне обмеження кешу поки залишається. Європейський DMA тисне на Apple,
але реальних змін у 2026 році поки немає.
✅ Висновки та чек-лист перевірки
Коли я інтегрував PWA у свій проєкт Kazki AI — сервіс
персоналізованих аудіоказок для дітей на Spring Boot — я зіткнувся з більшістю
помилок із цього списку. Не в теорії, а на продакшені, з реальними користувачами. Саме тому
я вирішив зібрати цей матеріал: кожен сценарій тут — це або мій власний досвід, або
проблема, яку я спостерігав у колег-розробників.
Головний висновок, до якого я прийшов: PWA — це не "додав manifest.json і готово".
Це архітектурне рішення, яке впливає на кешування, аналітику, SEO, авторизацію та поведінку
на різних платформах. На Android все працює майже ідеально — користувачі Kazki AI отримали
автоматичний банер встановлення, миттєве завантаження сторінок і повноекранний режим без
адресного рядка. На iOS ситуація складніша — автоматичного банеру немає, кеш може зникнути
через тиждень неактивності, і користувачам потрібно пояснювати, як додати додаток на екран
через меню "Поділитися" в Safari.
Найнебезпечніша помилка, на мою думку — це кешування HTML зі стратегією Cache First.
Вона невидима: сайт працює, сторінки відкриваються, але користувач бачить застарілу версію
і навіть не підозрює про це. У випадку Kazki AI, де контент генерується динамічно —
нові казки, лімити підписок, персоналізація — це було б критично. Тому я з самого початку
обрав Network First для HTML і повністю виключив з кешування авторизовані сторінки,
API-запити та аудіофайли. Результат: сайт завантажується швидше, офлайн показує хоча б
базову оболонку, а дані завжди актуальні.
Ще один важливий урок — версіонування кешу. Після кожного деплою я змінюю
CACHE_NAME (наприклад, з kazki-v2 на kazki-v3),
і старий кеш автоматично видаляється. Це просте правило, але без нього користувачі
залишаються на старій версії сайту на невизначений час.
Мій чек-лист перед кожним деплоєм PWA:
1. Кешування: HTML — Network First, статика — Cache First з версіонуванням.
2. Оновлення SW: skipWaiting() + clients.claim() + нова версія кешу.
3. Роутинг: API, адмін, авторизація, платежі — виключені з обробки SW.
4. Аналітика: pageview відправляється при кожному переході (актуально для SPA).
5. Canonical: кожна сторінка має canonical URL без UTM-параметрів.
6. HTTPS: всі ресурси, включаючи CDN та піддомени — через HTTPS.
7. Scope: manifest.json має явний scope, зовнішні посилання — target="_blank".
8. iOS: критичні дані — на сервері, клієнтський кеш — лише для швидкості.
Цей чек-лист я проходжу перед кожним релізом Kazki AI. Він займає 5 хвилин, але рятує
від годин дебагу та незадоволених користувачів. Сподіваюсь, мій досвід допоможе вам
уникнути тих самих граблів і запустити PWA, який дійсно покращить досвід ваших користувачів.
Якщо ви тільки починаєте знайомство з PWA — рекомендую спочатку прочитати мою попередню статтю
Progressive Web Apps (PWA) — повний гід для бізнесу та розробників,
де я детально розглянув що таке PWA, як вони працюють, порівняння з нативними додатками
та покрокову інструкцію створення.
📖 Джерела та корисні посилання
Ключові слова:
PWAWorkeriOSService Worker