Пользователь открывает сайт, а через пару дней хочет найти его среди приложений на телефоне. Обычный сайт так не умеет. Нативное приложение решает это, но требует отдельной кодовой базы, ревью в магазине и недель разработки. Progressive Web App — это третий путь: обычный сайт, который браузер умеет установить на домашний экран, запустить без адресной строки и обслужить без сети.
Что такое PWA
PWA — это не отдельный фреймворк и не формат сборки. Это набор стандартных веб-возможностей поверх обычного сайта:
- Web App Manifest — JSON-файл, описывающий приложение операционной системе (имя, иконка, режим окна).
- Service Worker — скрипт-прокси между страницей и сетью, который перехватывает запросы и умеет отдавать ответы из локального кеша.
- Cache API — хранилище ответов, которым управляет service worker.
Сложенные вместе, они дают браузеру право предложить установку и позволяют приложению работать без сети. «Progressive» означает постепенность: на старом браузере это просто сайт, на современном — устанавливаемое приложение с офлайном и push-уведомлениями.
Web App Manifest
Раньше браузер ничего не знал об устанавливаемом приложении — он просто показывал страницу. Manifest — это файл, который говорит браузеру: «это приложение, вот его имя, иконка и стартовый адрес».
Подключают его в <head> страницы:
<link rel="manifest" href="/manifest.webmanifest">
Пример минимального manifest:
{
"name": "Складской учёт",
"short_name": "Склад",
"start_url": "/",
"display": "standalone",
"theme_color": "#2F5B4F",
"icons": [
{ "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/512.png", "sizes": "512x512", "type": "image/png" }
]
}
Что здесь важно:
name— полное имя приложения,short_name— короткое (показывается под иконкой на домашнем экране).start_url— адрес, с которого откроется приложение после установки. Лучше задавать явно, иначе браузер берёт URL страницы, к которой подключён manifest.display: standaloneубирает адресную строку и кнопки браузера — приложение выглядит как нативное.theme_colorкрасит системные элементы вокруг окна (статус-бар, панель задач).icons— минимальный набор: 192 × 192 и 512 × 512 px.
Service Worker и офлайн
Раньше, если пользователь терял сеть, страница просто переставала работать. Service worker решает это: он перехватывает каждый сетевой запрос страницы и может ответить из локального кеша.
Service worker — отдельный скрипт, который браузер запускает в фоне. У него три ключевых момента жизни:
install— срабатывает один раз после регистрации; здесь наполняют кеш нужными файлами.activate— старые версии worker закрыты; здесь удаляют устаревший кеш.fetch— вызывается на каждый сетевой запрос страницы; здесь решают, что отдать.
const CACHE = "app-v1";
const ASSETS = ["/", "/index.html", "/app.js", "/styles.css"];
self.addEventListener("install", (event: ExtendableEvent) => {
event.waitUntil(caches.open(CACHE).then((c) => c.addAll(ASSETS)));
});
self.addEventListener("activate", (event: ExtendableEvent) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
)
);
});
self.addEventListener("fetch", (event: FetchEvent) => {
event.respondWith(
caches.match(event.request).then((hit) => hit ?? fetch(event.request))
);
});
Стратегия выше — cache-first: сначала смотрим в кеш, и только если ничего нет — идём в сеть. Это самая простая стратегия для статических файлов.
Регистрируют worker со страницы один раз:
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js");
}
Cache API (caches.open, addAll, match) — это хранилище, где ключом служит сетевой запрос, а значением — сохранённый ответ сервера. Подробнее о долговременном хранении данных — в статье про офлайн и хранилища.
Установка на домашний экран
Чтобы браузер предложил установку, нужно выполнить три условия:
- Подключён manifest.
- Зарегистрирован service worker.
- Сайт отдаётся по HTTPS (для локальной разработки годятся
localhostи127.0.0.1).
При выполненных условиях Chromium-браузеры стреляют событием beforeinstallprompt. Его можно перехватить, чтобы показать собственную кнопку вместо стандартной подсказки:
let deferred: BeforeInstallPromptEvent | null = null;
window.addEventListener("beforeinstallprompt", (e) => {
e.preventDefault();
deferred = e as BeforeInstallPromptEvent;
showInstallButton();
});
async function install() {
if (!deferred) return;
await deferred.prompt();
deferred = null;
}
После установки приложение появляется в лаунчере и запускается в окне без адресной строки — согласно display из manifest.
Ограничения на iOS
iOS — главная причина, по которой PWA не всегда достаточно.
Safari не поддерживает событие beforeinstallprompt: пользователь может добавить приложение на домашний экран только вручную через «Поделиться → На экран „Домой"». Браузер не подскажет сам.
Другие ограничения:
- Push-уведомления появились в Safari лишь с iOS 16.4, и только для PWA, уже добавленных на домашний экран — в самом браузере push недоступен.
- Фоновое выполнение отсутствует: service worker засыпает, как только приложение сворачивают.
- Часть API недоступна: Web Bluetooth, Web NFC и ряд других платформенных возможностей Safari не поддерживает.
- Квоты хранилища: браузер может вытеснить кешированные данные при нехватке места.
Когда этих ограничений становится слишком много, веб упаковывают в нативную обёртку — Capacitor даёт доступ к платформенным API и настоящие push-уведомления через push-уведомления.
Коротко
- PWA — это обычный сайт с manifest, service worker и Cache API: браузер умеет его установить и обслужить без сети.
- Manifest описывает приложение ОС: имя, иконка,
start_url, режим окна (standalone). - Service worker перехватывает сетевые запросы; в обработчике
fetchрешает, отдать из кеша или пойти в сеть. - Для установки нужны три условия: manifest + service worker + HTTPS.
beforeinstallprompt— событие Chromium, позволяет показать свою кнопку установки; Safari его не поддерживает.- Главные ограничения iOS: установка только вручную, push с 16.4 и только для установленных PWA, нет фонового выполнения.
- Если возможностей PWA не хватает — следующий шаг Capacitor.
Что почитать дальше
- Офлайн и хранилища — как хранить данные локально и управлять офлайн-режимом.
- Push-уведомления — как работают push через Web Push API и через Capacitor.
- Capacitor: веб-приложение в нативной обёртке — когда PWA недостаточно.