Прежде чем углубляться в детали, полезно увидеть общую картину: сколько именно действий отделяют обычный сайт от устанавливаемого PWA. Ответ — три. После этих трёх шагов Chrome уже предложит пользователю «Добавить приложение на главный экран», а сайт начнёт работать офлайн.
В этой главе пройдём минимальный путь от и до — на уровне «вот что это такое, вот зачем нужно, вот файл, положите его сюда». В следующих главах разберём каждую часть детально, но сейчас задача — чтобы в голове сложилась полная последовательность.
Шаг 1. HTTPS на домене
HTTPS — это обычный HTTP, но завёрнутый в шифрование. Любой трафик между браузером пользователя и вашим сервером идёт по защищённому каналу: даже если кто-то подключится к той же Wi-Fi сети в кафе, он не сможет прочитать содержимое запросов или подменить ответ. Технически HTTPS работает поверх протоколов TLS (старое название — SSL), и для его включения сайту нужен сертификат — цифровой документ, подтверждающий, что сайт действительно принадлежит указанному домену.
На обычном сайте HTTPS полезен, но не обязателен — сайт работает и по HTTP. В PWA HTTPS обязателен. Браузер откажется регистрировать service worker, если страница загружена не по HTTPS, и без service worker никакого PWA не получится.
Почему именно так строго? Service worker — это фоновый JavaScript, который умеет перехватывать все сетевые запросы страницы и подменять ответы. Если бы такую возможность можно было получить через незащищённое соединение, то злоумышленник в публичной Wi-Fi мог бы подсунуть свой service worker на любой сайт — и он перехватывал бы пароли, банковские данные и всё остальное. Поэтому браузеры пускают service worker только на проверенные источники.
Одно исключение для разработки. На localhost и 127.0.0.1 service worker работает по HTTP — браузер считает локальный адрес доверенным. Это значит, что на этапе разработки никакие сертификаты настраивать не нужно. Запустили локальный сервер на http://localhost:3000 — и пишите код.
На проде: два самых простых варианта получить HTTPS бесплатно.
- Let’s Encrypt — бесплатный центр сертификации. Выдаёт сертификаты автоматически через утилиту certbot, которая ставится на сервер, получает сертификат и продлевает его каждые 90 дней. Один раз настроили — и забыли.
- Cloudflare — сервис-прослойка перед сайтом. Переключаете DNS домена на Cloudflare, и они выдают свой SSL поверх вашего сайта бесплатно. Настройка — около десяти минут, ничего ставить на сервер не надо.
Как проверить. Откройте сайт — в адресной строке должен быть значок замка слева от URL. Тапните по нему — браузер покажет, что соединение защищено и какой сертификат у сайта.
Шаг 2. Манифест приложения
Манифест — это маленький JSON-файл, в котором написано всё, что нужно операционной системе, чтобы показать ваш сайт как приложение. Имя, иконки, цвета, режим запуска. По сути это «паспорт» вашего PWA: ОС читает его, когда пользователь устанавливает приложение, и потом использует данные оттуда для отрисовки.
Создайте в корне сайта файл app.webmanifest (или manifest.json — оба варианта работают, нюансы разберём в главе 3) с таким содержимым:
{
"name": "Минимальное PWA",
"short_name": "MinPWA",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#316bbe",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Расшифровка полей, чтобы было понятно, зачем каждое:
- name — полное имя приложения. Показывается на сплэш-экране, который пользователь видит, пока загружается основной интерфейс после нажатия на иконку.
- short_name — короткое имя для подписи под иконкой на главном экране. Места там мало, поэтому здесь обычно 8–12 символов.
- start_url — URL, который открывается по тапу на иконку. Если PWA должна стартовать с главной, пишут "/".
- display: standalone — говорит ОС: «запусти приложение в собственном окне, без адресной строки браузера и вкладок». Выглядит как нативное приложение.
- background_color — цвет фона сплэш-экрана.
- theme_color — цвет шапки приложения. На Android окрашивает строку состояния телефона.
- icons — массив иконок разного размера. Минимум для Chrome — две: 192×192 и 512×512 пикселей. Меньшая используется на иконке главного экрана, бо́льшая — на сплэш-экране и в магазинах приложений, если PWA туда будут публиковать.
Зачем именно две иконки разных размеров? Экраны телефонов и десктопов разные, плотность пикселей разная, а ОС хочет, чтобы иконка выглядела чётко везде. Если положить только маленькую — на большом экране её придётся растягивать, и она будет мыльной. Если только большую — на маленьком ОС будет её сжимать каждый раз при отрисовке, что медленнее. Поэтому отдают сразу два варианта, чтобы система могла выбрать подходящий.
Когда файл готов, подключите его в HTML-каркасе на каждой странице сайта:
<link rel="manifest" href="/app.webmanifest">
<meta name="theme-color" content="#316bbe">
Тег <link rel="manifest"> говорит браузеру: «на этом адресе лежит описание приложения». Мета-тег theme-color дублирует значение из манифеста — на случай, если страница уже отрисована, а манифест браузер ещё не скачал. Так строка состояния телефона окрашивается сразу при первом рендере, не дёргается.
Как проверить. Откройте сайт в Chrome, нажмите F12 (или Cmd+Opt+I на Mac), перейдите на вкладку Application в DevTools, выберите слева пункт Manifest. Браузер покажет распарсенный манифест, иконки в превью и все ошибки/предупреждения, если что-то не так.
Шаг 3. Service worker
Service worker — это JavaScript-файл, который выполняется в отдельном фоновом потоке браузера, независимо от страниц сайта. Он не имеет доступа к DOM (то есть не может ничего нарисовать на странице, не видит, что показывается пользователю), но умеет другое: перехватывать сетевые запросы, идущие со страницы. Любой fetch в браузере проходит через service worker, и тот решает, что вернуть в ответ — пойти в сеть, отдать из кэша, скомбинировать.
Именно service worker отвечает за работу офлайн: при первом визите он сохраняет важные файлы в кэш, а при последующих визитах без сети — отдаёт их из кэша вместо ошибки «нет интернета».
Создайте файл sw.js в корне сайта:
const CACHE_NAME = "minimal-pwa-v1";
const PRECACHE = ["/", "/index.html", "/offline.html"];
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE))
);
});
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request).catch(() => caches.match("/offline.html"));
})
);
});
Разберём, что происходит:
- CACHE_NAME — имя кэша. Каждый кэш в браузере именованный; в одном сайте их может быть несколько (один под статику, другой под аватарки и так далее). Версия в имени (-v1) пригодится позже — при выкатке новой версии меняют на -v2, и старый кэш можно удалить.
- PRECACHE — список файлов, которые надо положить в кэш сразу при установке service worker. Тут — главная, основной HTML и оффлайн-страница для случая «нет сети».
- Событие install — срабатывает один раз при регистрации service worker. Открываем кэш с нашим именем, добавляем туда файлы из списка. event.waitUntil() говорит браузеру: «не считай установку завершённой, пока этот промис не разрешится».
- Событие fetch — срабатывает на каждый сетевой запрос, который идёт со страницы. Сначала ищем файл в кэше; если нашли — отдаём из кэша; если нет — идём в сеть; если сети тоже нет — отдаём оффлайн-страницу.
Сам файл создан, но пока он лежит мёртвым грузом — браузер о нём ничего не знает. Нужна регистрация: отдельный код на главной странице, который скажет браузеру «вот этот файл — service worker, начни его использовать».
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker
.register("/sw.js")
.then(() => console.log("Service worker зарегистрирован"))
.catch((err) => console.warn("Ошибка регистрации SW:", err));
});
}
Что здесь важно:
- "serviceWorker" in navigator — страховка от старых браузеров, в которых API нет. На современных всегда даёт true.
- Регистрация после load, а не в начале <script>. Если зарегистрировать раньше — браузер начнёт качать sw.js параллельно с первичной загрузкой страницы, и сайт начнёт визуально подгружаться медленнее. Ждём, пока страница отрисуется, и только потом подгружаем инфраструктуру.
- navigator.serviceWorker.register("/sw.js") — собственно регистрация. Промис разрешается успешным .then, если всё прошло, или падает в .catch, если в файле SW синтаксическая ошибка или путь неверный.
Как проверить. В Chrome DevTools (F12) откройте вкладку Application → раздел Service Workers. После первой загрузки страницы там должна появиться запись с зелёной точкой и статусом activated and is running. Это значит, что service worker зарегистрирован и работает.
Что произойдёт после этих трёх шагов
При первом заходе пользователя:
- Браузер скачивает sw.js, регистрирует service worker. Тот срабатывает на событие install и предзагружает в кэш главную, index.html и offline.html.
- Параллельно браузер видит подключённый манифест с обязательными полями. Через 5–10 секунд он показывает в адресной строке иконку «Установить приложение» (на десктопе) или предлагает добавить на главный экран (на Android).
- Если пользователь нажмёт «установить», на устройстве появляется иконка с дизайном из манифеста. При тапе на иконку запускается приложение в собственном окне без UI браузера — ровно как нативное.
- Если интернет пропал во время следующего визита — страницы открываются из кэша; если запрашиваемой страницы в кэше нет, показывается offline.html вместо обычной ошибки ERR_INTERNET_DISCONNECTED.
Этого минимума достаточно, чтобы Lighthouse-аудит PWA прошёл с зелёной плашкой. Дальше идут улучшения: продуманные стратегии кэширования (глава 5), push-уведомления (глава 6), кастомная кнопка установки и публикация в магазинах (глава 7). Этим займёмся дальше.
Чек-лист одной строкой
- HTTPS на проде (или localhost на разработке) — обязательно, иначе SW не зарегистрируется.
- app.webmanifest в корне + <link rel="manifest"> в HTML на каждой странице.
- sw.js в корне + регистрация через navigator.serviceWorker.register() на главной.
Каждый из этих трёх пунктов — тема собственной главы, в которой разберём детали и реальные нюансы.