
Progressive Web App (PWA) — це звичайний веб-сайт, який браузер може встановити на пристрій як нативний додаток. Користувач отримує іконку на робочому столі або в меню, додаток відкривається без адресного рядка, працює офлайн і може надсилати push-сповіщення. Для розробника — це той самий HTML/CSS/JS, без App Store і без нативної розробки.
Для перетворення сайту на PWA потрібні три речі: HTTPS, Web App Manifest і Service Worker.
PWA працює тільки на захищеному з'єднанні. Якщо сайт вже на HTTPS — цей пункт вже виконано. Для локальної розробки localhost є виключенням і вважається безпечним.
Manifest — це JSON-файл, який описує додаток: назву, іконки, кольори, режим відображення. Саме він дозволяє браузеру запропонувати користувачу «встановити» сайт.
Створіть файл manifest.json в корені сайту:
{
"name": "Моя Крамниця",
"short_name": "Крамниця",
"description": "Онлайн-магазин товарів для дому",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1a73e8",
"orientation": "portrait-primary",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
Основні параметри:
name — повна назва (відображається при встановленні).short_name — коротка назва під іконкою (до 12 символів).start_url — яка сторінка відкривається при запуску. Можна додати UTM: / або /?source=pwa.display — режим відображення: standalone (без браузерного UI), fullscreen, minimal-ui, browser.theme_color — колір рядка статусу і заголовка браузера.background_color — колір фону сплеш-екрану при завантаженні.icons — обов'язково потрібні розміри 192×192 і 512×512 у PNG.Підключіть manifest у <head> кожної сторінки:
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#1a73e8">
Для Safari (iOS) додатково:
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Крамниця">
<link rel="apple-touch-icon" href="/icons/icon-192.png">
iOS досі не підтримує стандартний manifest повністю, тому ці мета-теги обов'язкові для нормальної роботи на iPhone.
Service Worker — це JavaScript-файл, який браузер реєструє окремо від сторінки. Він перехоплює мережеві запити і може відповідати з кешу — саме це дає офлайн-режим і швидке завантаження.
Додайте в основний JS або перед </body>:
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('SW зареєстровано:', reg.scope))
.catch(err => console.error('SW помилка:', err));
});
}
Мінімальний service worker з кешуванням статики:
const CACHE_NAME = 'my-site-v1';
const STATIC_ASSETS = [
'/',
'/index.html',
'/css/style.css',
'/js/app.js',
'/icons/icon-192.png',
'/icons/icon-512.png',
'/offline.html'
];
// Встановлення — кешуємо статику
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(STATIC_ASSETS))
.then(() => self.skipWaiting())
);
});
// Активація — видаляємо старі кеші
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys.filter(key => key !== CACHE_NAME)
.map(key => caches.delete(key))
)
).then(() => self.clients.claim())
);
});
// Fetch — спочатку кеш, потім мережа
self.addEventListener('fetch', event => {
// Ігноруємо не-GET запити і запити до інших доменів
if (event.request.method !== 'GET') return;
if (!event.request.url.startsWith(self.location.origin)) return;
event.respondWith(
caches.match(event.request)
.then(cached => {
if (cached) return cached;
return fetch(event.request)
.then(response => {
// Кешуємо успішні відповіді
if (response.status === 200) {
const clone = response.clone();
caches.open(CACHE_NAME)
.then(cache => cache.put(event.request, clone));
}
return response;
})
.catch(() => {
// Якщо мережі немає — показуємо офлайн-сторінку
if (event.request.headers.get('accept').includes('text/html')) {
return caches.match('/offline.html');
}
});
})
);
});
Вище показана стратегія Cache First (спочатку кеш) — підходить для статики. Для динамічного контенту краще Network First (спочатку мережа, кеш як запасний варіант):
// Network First для API-запитів
self.addEventListener('fetch', event => {
if (event.request.url.includes('/api/')) {
event.respondWith(
fetch(event.request)
.then(response => {
const clone = response.clone();
caches.open(CACHE_NAME)
.then(cache => cache.put(event.request, clone));
return response;
})
.catch(() => caches.match(event.request))
);
}
});
Створіть /offline.html — проста сторінка, яка показується коли немає мережі і потрібний URL не в кеші:
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Немає з'єднання</title>
<style>
body { font-family: sans-serif; text-align: center; padding: 60px 20px; color: #333; }
h1 { font-size: 24px; }
p { color: #666; }
</style>
</head>
<body>
<h1>???? Немає з'єднання з інтернетом</h1>
<p>Перевірте мережу і спробуйте ще раз.</p>
<button onclick="location.reload()">Оновити сторінку</button>
</body>
</html>
Мінімально потрібні два розміри: 192×192 і 512×512 px у PNG. Для purpose: "maskable" важливо, щоб основний контент іконки вміщувався у центральних 80% площі — решта може бути обрізана заокругленою маскою Android.
Згенерувати всі розміри автоматично можна через realfavicongenerator.net або maskable.app для перевірки maskable-варіанту.
Відкрийте Chrome DevTools → вкладка Application:
activated and running.Також запустіть Lighthouse (DevTools → вкладка Lighthouse → категорія Progressive Web App) — він перевіряє всі критерії PWA і показує що не так.
У Chrome на десктопі кнопка встановлення з'явиться в адресному рядку (іконка монітора зі стрілкою). На Android Chrome покаже банер «Додати на головний екран» після кількох відвідувань.
Коли потрібно оновити кеш — змініть константу CACHE_NAME (наприклад, my-site-v2). При наступному відвідуванні service worker завантажить нову версію, а при активації видалить старий кеш.
Якщо потрібно оновлення відразу при новому деплої — додайте в activate:
self.addEventListener('activate', event => {
event.waitUntil(self.clients.claim());
});
І при реєстрації в HTML:
navigator.serviceWorker.register('/sw.js')
.then(reg => {
reg.addEventListener('updatefound', () => {
const newWorker = reg.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'activated') {
// Можна показати повідомлення "Доступна нова версія"
location.reload();
}
});
});
});
Якщо немає бажання писати service worker вручну — є готові інструменти:
Workbox (від Google) — бібліотека, яка генерує service worker з потрібними стратегіями кешування через конфіг. Підходить для складних сайтів з різними типами ресурсів.
WordPress — плагіни Super PWA або PWA for WP & AMP додають маніфест і service worker без написання коду.
OpenCart — кастомні модулі або ручне додавання маніфесту і SW через редактор шаблонів.
Мінімальний PWA — це три файли: manifest.json, sw.js і offline.html, плюс кілька рядків у <head>. Для більшості сайтів це реалізується за пів дня. Результат: встановлюваний додаток без App Store, кешована статика для швидкого завантаження і базовий офлайн-режим.
Для просунутих сценаріїв (push-сповіщення, фонова синхронізація, складне кешування) є Workbox і розширений Service Worker API — але це вже тема для окремої статті.
Правильно налаштований віджет банки Монобанк підвищить довіру відвідувачів до збору. Використовуйте ..
Коли ви працюєте з PHP скриптами, які обробляють великі обсяги даних через POST запити, ви можете зі..
Повноцінна graph розмітка трансформує присутність компанії в пошукових системах. Збільшується видимі..