← назад к разделу

Пользователь открывает сайт, а через пару дней хочет найти его среди приложений на телефоне. Обычный сайт так не умеет. Нативное приложение решает это, но требует отдельной кодовой базы, ревью в магазине и недель разработки. 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) — это хранилище, где ключом служит сетевой запрос, а значением — сохранённый ответ сервера. Подробнее о долговременном хранении данных — в статье про офлайн и хранилища.

Установка на домашний экран

Чтобы браузер предложил установку, нужно выполнить три условия:

  1. Подключён manifest.
  2. Зарегистрирован service worker.
  3. Сайт отдаётся по 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 недостаточно.