PWA для бизнеса: что умеет веб-приложение и когда оно заменит мобильное

Как устроены Progressive Web Apps: манифест, service worker, кэширование, push-уведомления. Что реально работает на iPhone после iOS 16.4, проверяемые кейсы Twitter Lite, Pinterest и Starbucks, сравнение с нативным приложением и чек-лист внедрения.

Progressive Web App, или PWA, — это сайт, который умеет вести себя как приложение: устанавливается на экран телефона или компьютера, открывается в отдельном окне без адресной строки, работает при плохой связи и присылает push-уведомления. Для бизнеса это способ получить часть возможностей мобильного приложения, не разрабатывая и не поддерживая отдельные версии под iOS и Android. Вокруг PWA много преувеличений: одни обещают, что веб-приложение полностью заменит нативное, другие до сих пор считают, что на iPhone оно ничего не умеет. Ниже — как PWA устроено технически, что реально поддерживают браузеры и iOS на сегодня, какие цифры из публичных кейсов можно проверить и по каким признакам понять, подходит ли PWA вашему продукту. Что делает сайт прогрессивным веб-приложением PWA — не фреймворк и не отдельная технология, а набор веб-стандартов. Сайт на React, Vue, Svelte или на обычном серверном рендеринге становится PWA, когда в нём есть три вещи. HTTPS. Service worker и большинство «приложенческих» API работают только в защищённом контексте. Исключение — localhost при разработке. Web App Manifest. JSON-файл с названием, иконками, цветами и режимом отображения. По нему браузер понимает, как показывать установленное приложение. Service worker. Скрипт, который работает отдельно от страницы, перехватывает сетевые запросы и может отвечать из кэша, принимать push-сообщения и выполнять фоновые задачи. Требования к установке со временем смягчились. В Chrome на компьютерах и Android пользователь может установить как приложение практически любой сайт, а манифест нужен, чтобы браузер сам предлагал установку и корректно показывал иконку и название. В iOS 26 любой сайт, добавленный на экран «Домой», по умолчанию открывается как веб-приложение. При этом service worker остаётся основой всего, ради чего PWA затевают: офлайн-режима, быстрой повторной загрузки и уведомлений. Манифест { "name": "Сервисный портал", "short_name": "Портал", "id": "/", "start_url": "/?source=pwa", "scope": "/", "display": "standalone", "theme_color": "#1a1a2e", "background_color": "#ffffff", "lang": "ru", "icons": [ { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }, { "src": "/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } ] } Манифест подключается тегом link rel="manifest" href="/manifest.webmanifest" . Маскируемую иконку лучше выделить в отдельный файл: у неё должен быть запас по краям, иначе Android обрежет логотип. Параметр в start_url помогает отличать в аналитике запуски установленного приложения от обычных визитов. Service worker: как PWA работает без сети Service worker регистрируется со страницы и дальше живёт своей жизнью: устанавливается, активируется и получает события fetch для всех запросов в своей области действия. // main.js — регистрация if ('serviceWorker' in navigator) { window.addEventListener('load', () = { navigator.serviceWorker.register('/sw.js').catch((error) = { console.error('Service worker не зарегистрирован:', error); }); }); } // sw.js — предзагрузка оболочки и очистка старых версий кэша const CACHE = 'shell-v3'; const SHELL = ['/', '/offline.html', '/app.css', '/app.js']; self.addEventListener('install', (event) = { event.waitUntil(caches.open(CACHE).then((cache) = cache.addAll(SHELL))); }); self.addEventListener('activate', (event) = { event.waitUntil( caches.keys().then((keys) = Promise.all(keys.filter((key) = key !== CACHE).map((key) = caches.delete(key))) ) ); }); self.addEventListener('fetch', (event) = { if (event.request.mode === 'navigate') { event.respondWith( fetch(event.request).catch(() = caches.match('/offline.html')) ); } }); Обратите внимание на событие activate : без удаления старых кэшей пользователи месяцами получают устаревшие файлы, а занятое место растёт. Это самая частая ошибка в самописных service worker. Стратегии кэширования Универсальной стратегии нет. В одном приложении обычно сочетаются три подхода. Cache First. Сначала кэш, сеть — только если в кэше пусто. Подходит для статики с хешем в имени файла: скриптов, стилей, шрифтов, иконок. Такой файл никогда не меняется, меняется только его имя. Network First. Сначала сеть, кэш — запасной вариант. Подходит для данных, которые должны быть свежими: заказов, статусов, остатков. Без сети пользователь увидит последнее сохранённое состояние. Stale While Revalidate. Сразу отдаём ответ из кэша и параллельно обновляем его из сети. Подходит для справочников, каталогов, аватаров — всего, где задержка обновления на один визит допустима. Вручную эти стратегии пишутся с ошибками, поэтому на практике используют библиотеку Workbox или плагины сборщиков на её основе, например vite-plugin-pwa. // sw.js на Workbox import { precacheAndRoute } from 'workbox-precaching'; import { registerRoute } from 'workbox-routing'; import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'; import { ExpirationPlugin } from 'workbox-expiration'; // Список файлов сборки подставляет плагин сборщика precacheAndRoute(self.__WB_MANIFEST); registerRoute( ({ request }) = request.destination === 'image', new CacheFirst({ cacheName: 'images', plugins: [new ExpirationPlugin({ maxEntries: 200, maxAgeSeconds: 30 * 24 * 60 * 60 })], }) ); registerRoute( ({ url }) = url.pathname.startsWith('/api/orders'), new NetworkFirst({ cacheName: 'orders', networkTimeoutSeconds: 3 }) ); registerRoute( ({ url }) = url.pathname.startsWith('/api/catalog'), new StaleWhileRevalidate({ cacheName: 'catalog' }) ); Одно правило важнее выбора стратегии: не кэшируйте в service worker персональные данные на общих устройствах без продуманного выхода из аккаунта. При выходе кэши с пользовательскими ответами нужно очищать явно. Push-уведомления Веб-push работает по стандарту Web Push: браузер создаёт подписку у своего push-сервиса, ваш сервер отправляет на неё зашифрованное сообщение, подписанное VAPID-ключами, а service worker показывает уведомление, даже если вкладка закрыта. // Подписка — вызывать только по действию пользователя, например по кнопке async function subscribeToPush(publicVapidKey) { const permission = await Notification.requestPermission(); if (permission !== 'granted') return; const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: publicVapidKey, // строка base64url или Uint8Array }); await fetch('/api/push/subscriptions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(subscription), }); } // sw.js — показ уведомления и переход по клику self.addEventListener('push', (event) = { const data = event.data ? event.data.json() : {}; event.waitUntil( self.registration.showNotification(data.title || 'Новое событие', { body: data.body, icon: '/icons/icon-192.png', data: { url: data.url || '/' }, }) ); }); self.addEventListener('notificationclick', (event) = { event.notification.close(); event.waitUntil(clients.openWindow(event.notification.data.url)); }); Практические правила: не запрашивайте разрешение при первом заходе — браузеры и пользователи наказывают за это отказами. Объясните, какие уведомления человек будет получать, и спросите в момент, когда это уместно: после оформления заказа, при подписке на статус заявки. Храните подписки на сервере и удаляйте те, на которые push-сервис отвечает ошибкой «подписка больше не существует». Офлайн-формы и фоновая синхронизация Типичный сценарий для полевых сотрудников: заполнили акт на объекте, где нет связи, и данные должны уйти на сервер, когда сеть появится. Для этого есть Background Sync API, но поддерживают его только браузеры на Chromium — Chrome, Edge, Opera, Samsung Internet. В Safari и Firefox его нет. Поэтому надёжная схема одна для всех платформ: приложение сохраняет неотправленные данные в IndexedDB, показывает пользователю статус «ожидает отправки» и повторяет отправку при следующем открытии или при событии online . Background Sync там, где он есть, лишь ускоряет отправку, но не заменяет очередь. PWA на iPhone: что работает на самом деле Именно iOS чаще всего решает, подходит ли PWA продукту, поэтому разберём по пунктам. Push-уведомления появились в iOS и iPadOS 16.4 в 2023 году. Условие — пользователь должен добавить сайт на экран «Домой»: в обычной вкладке Safari веб-push на iPhone не работает. Уведомления приходят на экран блокировки, в Центр уведомлений и на Apple Watch, как у обычных приложений. Значки на иконке. Badging API поддерживается с той же версии 16.4 — можно показывать счётчик непрочитанных. Установка из других браузеров. С iOS 16.4 добавить сайт на экран «Домой» можно не только из Safari, но и из сторонних браузеров через меню «Поделиться». Нет системного приглашения к установке. Событие beforeinstallprompt , через которое на Android показывают свою кнопку «Установить», в Safari не поддерживается. На iPhone пользователю нужно объяснить путь «Поделиться → На экран „Домой“» — обычно короткой подсказкой с картинкой. Хранилище. С Safari 17 квота для сайта в браузере рассчитывается от объёма диска и может достигать 60% от него, для других приложений на WebKit — до 15%. Старое представление о «50 МБ на iOS» устарело. Нет Background Sync, Web Bluetooth, Web NFC. Эти возможности на iPhone недоступны, и в планах такого не видно. Отдельная история — Евросоюз. В начале 2024 года Apple объявила, что в iOS 17.4 для пользователей в ЕС веб-приложения на экране «Домой» перестанут работать как отдельные приложения, объяснив это требованиями Закона о цифровых рынках. После волны критики 1 марта 2024 года компания отменила решение, и поддержка сохранилась на прежней основе — на движке WebKit. Для российских пользователей эта история не имела прямых последствий, но хорошо показывает главный риск PWA на iOS: возможности веб-приложений на iPhone определяет одна компания. Android и компьютеры На Android поддержка шире. Chrome предлагает установку сам, установленное приложение появляется в списке приложений, работают push, фоновая синхронизация и значительная часть аппаратных API. PWA можно опубликовать в Google Play через Trusted Web Activity — это обёртка, которая открывает ваш сайт в полноэкранном режиме браузера; готовые инструменты вроде Bubblewrap и PWABuilder собирают такой пакет. На Windows установленные PWA из Chrome и Edge выглядят как обычные программы с отдельным окном и ярлыком. Для корпоративных систем это важный сценарий: сотрудники работают с порталом, CRM или складской системой в отдельном окне на компьютере и в установленном приложении на телефоне, а команда поддерживает один код. PWA или нативное приложение Критерий PWA Нативное или кроссплатформенное приложение Кодовая база Одна, общая с сайтом Отдельное приложение, часто плюс сайт Выпуск обновлений Сразу после деплоя, без модерации Через проверку App Store и Google Play Поиск Страницы индексируются поисковиками Содержимое приложения поиском не индексируется Установка С сайта; на iPhone — вручную через «Поделиться» Из магазина приложений Push на iPhone Только после добавления на экран «Домой» Без ограничений Bluetooth, NFC, фоновая геолокация Ограниченно на Android, на iPhone нет Полный доступ Встроенные покупки и подписки через магазин Нет Да Присутствие в магазине как канал привлечения Только Google Play и Microsoft Store через обёртки Да Подробное сравнение нативной и кроссплатформенной мобильной разработки — в статье «Нативная или кроссплатформенная разработка приложения» . Если выбор уже сделан в пользу приложения, этапы и стоимость описаны на странице разработки мобильного приложения . Публичные кейсы: что можно проверить Большинство цифр о PWA, которые кочуют по статьям, восходят к нескольким кейсам 2017–2018 годов. Они реальны, но важно понимать, с чем сравнивали: почти везде новую PWA сравнивали со старой мобильной версией сайта, а не с нативным приложением. Twitter Lite. По опубликованному на web.dev кейсу 2017 года, после запуска PWA