Розробка PWA-додатку на базі 1С-Бітрікс
Починаючи з версії 21.0, у Бітріксі є штатний модуль pwa — генерація manifest.json, реєстрація Service Worker, базовий offline-режим. На демонстрації виглядає переконливо: сайт додається на робочий стіл, працює без мережі, надсилає push-повідомлення. На реальному проєкті модуль закриває приблизно 40% потреб. Решта — стратегії кешування, сумісність із модулем composite, обхід обмежень iOS, доведення Lighthouse-метрик до зеленої зони — потребує ручної роботи.
Штатний модуль pwa: що він робить
Активація: Адміністрування → Налаштування → Налаштування модулів → PWA. Після ввімкнення Бітрікс виконує три дії:
- Генерує
/manifest.jsonз параметрів модуля —name,short_name,start_url,display,theme_color,background_color, масивicons[] - Реєструє Service Worker
/sw.jsчерезnavigator.serviceWorker.register('/sw.js')у шаблоні сайту - Додає
<link rel="manifest" href="/manifest.json">у<head>
Параметр display за замовчуванням standalone — додаток відкривається без адресного рядка. Якщо користувачам потрібна навігація назад через історію браузера — ставимо minimal-ui.
Іконки — модуль очікує повний набір: 72x72, 96x96, 128x128, 144x144, 152x152, 192x192, 384x384, 512x512 у PNG. Без іконки 512x512 Lighthouse знижує бали за installability. Генеруємо набір з одного вихідника через CFile::ResizeImageGet(), але перевіряємо якість вручну — автоматичне стиснення іноді дає артефакти на дрібних розмірах.
Service Worker та стратегії кешування
Штатний /sw.js використовує Cache First для статики і Network First для HTML. Цього досить для базового offline, але не для повноцінного PWA.
Cache First — Service Worker шукає відповідь у CacheStorage, якщо знаходить — повертає, не звертаючись до сервера. Працює для /bitrix/js/, /bitrix/css/, /upload/. Проблема: після деплою нового JS-бандла користувач продовжує отримувати стару версію. На сайті з частими релізами це означає застарілий JavaScript протягом годин.
Рішення — версіонування кешу. Service Worker зберігає константу версії. При activate видаляє всі кеші, крім поточної версії:
const CACHE_VERSION = 'v1.4';
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== CACHE_VERSION).map(k => caches.delete(k)))
)
);
});
Network First — спочатку запит до сервера, якщо сервер недоступний — fallback із кешу. Правильна стратегія для HTML, але є нюанс із модулем composite. Композитний сайт віддає статичний HTML із /bitrix/html_pages/ через Nginx — без ініціалізації PHP. Коли Service Worker із Cache First для HTML перехоплює відповідь, Nginx навіть не отримує запит. Якщо composite оновив сторінку (товар закінчився, ціна змінилася) — користувач бачить застарілі дані.
Stale While Revalidate — стратегія, яку штатний модуль не використовує, але ми додаємо для API та каталожних даних. Миттєво віддає з кешу, паралельно оновлює кеш запитом до сервера. Ідеально для списків товарів, що змінюються раз на годину.
Кастомну логіку пишемо у /local/templates/main/sw-custom.js і підключаємо через importScripts(). Не редагуємо /sw.js напряму — модуль його перегенерує.
Глибше про взаємодію з composite
Це найскладніший аспект PWA на Бітріксі, тому розберемо детально.
Модуль composite зберігає готовий HTML у файловій системі. Nginx віддає файл напряму — час відповіді 5-15ms. Динамічні зони (<div id="bx-composite-...">) підвантажуються окремим AJAX-запитом до bitrix/services/main/ajax.php після завантаження сторінки.
Конфлікт №1: подвійне кешування. composite кешує HTML на сервері. Service Worker кешує HTML на клієнті. Коли серверний кеш інвалідується (контент змінився), клієнтський кеш залишається старим. Користувач бачить застарілу сторінку навіть після F5 — бо Service Worker перехоплює запит до того, як він дійде до сервера.
Рішення: для навігаційних запитів (HTML) завжди використовуємо Network First. Service Worker спочатку йде до сервера (де composite віддає свіжий HTML), і лише при відсутності мережі повертає кеш.
Конфлікт №2: динамічні зони. AJAX-запити до bitrix/services/main/ajax.php несуть персоналізовані дані — ім'я користувача, кошик, кількість товарів. Якщо Service Worker кешує ці відповіді, наступний користувач побачить чужі дані.
Рішення: у Service Worker виключаємо з кешування:
- Усі запити до
bitrix/services/main/ajax.php - Запити з параметром
sessid - Запити з cookie
PHPSESSID(перевіряємо черезevent.request.headers)
Конфлікт №3: CDN. Якщо composite налаштований із CDN, HTML може містити абсолютні URL на CDN-домен. Service Worker працює в межах свого scope (зазвичай /) і не перехоплює запити до іншого origin. Статика з CDN проходить повз Service Worker — це нормально для CSS/JS, але якщо зображення критичні для offline, потрібно кешувати їх окремо через cache.add().
Push-повідомлення через модуль pull
Модуль pull (Push and Pull) забезпечує real-time взаємодію в Бітріксі. Для PWA він використовується як транспорт push-повідомлень.
Ланцюжок: клієнт підписується через PushSubscription API → надсилає endpoint, keys.p256dh, keys.auth на сервер → сервер зберігає підписку в таблиці b_pull_push → при настанні події відправляє payload через \Bitrix\Pull\Push::send().
Налаштування:
- Генеруємо VAPID-ключі:
openssl ecparam -genkey -name prime256v1 -out private.pem - Публічний ключ прописуємо в налаштуваннях модуля
pull→ «Push-повідомлення» → «VAPID Public Key» - У Service Worker обробляємо подію
pushіnotificationclickдля відкриття URL черезclients.openWindow()
Серверна частина — подія OnAfterOrderAdd або будь-який інший обробник викликає \Bitrix\Pull\Push::send() з масивом підписок і payload. Для масових розсилок (акції, нові товари) використовуємо черги через b_agent або окремий cron-скрипт, щоб не блокувати основний потік.
Обмеження iOS — окрема історія
PWA на iOS через Safari — це інший рівень підтримки порівняно з Chrome на Android:
-
Web Push — лише з iOS 16.4, і лише для сайтів, доданих на Home Screen. У звичайній вкладці Safari
PushManager.subscribe()поверне помилку -
beforeinstallprompt— не підтримується. Програмно показати банер встановлення неможливо. Показуємо ручну інструкцію: «Натисніть Поділитися → Додати на початковий екран» -
Background Sync — не підтримується. Форма, відправлена offline, не потрапить у чергу Sync API. Обхід: зберігаємо в IndexedDB, повторюємо при події
online - Badge API — не підтримується. Лічильник на іконці додатку недоступний
- Квота сховища — Safari обмежує кожен origin приблизно 50MB. Chrome виділяє до 80% вільного місця на диску
- Час життя Service Worker — Safari агресивно вивантажує Service Worker після кількох днів неактивності. Push-повідомлення перестають працювати, доки користувач не відкриє додаток знову
Для проєктів, де значна частка аудиторії на iOS, ці обмеження обговорюємо з клієнтом на старті. PWA на iOS — це ближче до закладки з offline-підтримкою, ніж до нативного додатку.
Lighthouse: на що звертаємо увагу
-
Installable — коректний
manifest.json, Service Worker із обробникомfetch, іконка 512x512,start_urlповертає 200 в offline - HTTPS — обов'язково. Налаштовуємо в Бітріксі: Налаштування → Головний модуль → «Працювати в режимі HTTPS». Nginx — один 301 без ланцюжка
-
theme-color—<meta name="theme-color">повинен збігатися зtheme_colorуmanifest.json -
Offline fallback — при відсутності мережі показуємо кастомну сторінку
/offline.html, а не Chrome Dinosaur -
start_url— не повинен повертати 3xx. На мультимовних сайтах із редіректом/→/uk/вказуємо/uk/у манифесті
PWA проти нативного додатку
| Критерій | PWA | Нативний (iOS/Android) |
|---|---|---|
| Встановлення | З браузера, без магазину | App Store / Google Play |
| Оновлення | Автоматичне через Service Worker | Через магазин, потрібне підтвердження |
| Доступ до пристрою | Камера, геолокація, повідомлення | Повний доступ до API пристрою |
| Offline | Кешований контент | Повноцінна offline-логіка |
| Розмір | 0 MB (кеш браузера) | 20-100+ MB |
| Вартість розробки | 1-2 тижні поверх сайту | 2-4 місяці окремий проєкт |
Етапи робіт
-
Аудит поточного сайту (2-3 дні) — перевірка сумісності шаблону з вимогами PWA, Lighthouse-аналіз, оцінка взаємодії з
composite -
Налаштування модуля та Service Worker (3-5 днів) — конфігурація
manifest.json, стратегії кешування, offline-сторінка, потік push-підписки - Тестування (2-3 дні) — Android (Chrome, Samsung Internet), iOS (Safari), десктоп (Chrome, Edge). Lighthouse audit на кожному етапі
- Запуск і моніторинг (1-2 дні) — деплой на прод, перевірка метрик, алерти на помилки Service Worker
| Масштаб | Терміни |
|---|---|
PWA для існуючого сайту з composite |
1-2 тижні |
| PWA + push-повідомлення + offline-каталог | 2-3 тижні |
| PWA з кастомним App Shell і складною offline-логікою | 3-5 тижнів |







