Разработка Progressive Web App (PWA)
Сайт теряет до 40% трафика, если пользователь уходит из-за медленной загрузки? Или каждый второй посетитель не возвращается после первого визита? Мы помогаем внедрить PWA под ключ — это решение, которое сочетает скорость веба и возможности нативных приложений. PWA устанавливается на домашний экран, работает офлайн и может отправлять push-уведомления. За 2–4 дня вы получаете приложение, которое грузится в 2 раза быстрее на медленных соединениях. По данным Google, PWA увеличивают конверсию на 36% и снижают отказы на 15%. Средняя стоимость привлечения клиента (CAC) при этом существенно снижается, что экономит бюджет на рекламу.
Как PWA решает проблему офлайн-доступа?
Основная боль: пользователь теряет связь — сайт недоступен. Service Worker кэширует ключевые ресурсы (App Shell) и страницы. Даже при обрыве соединения человек видит интерфейс, а не ошибку. Мы используем три стратегии кэширования в зависимости от типа данных:
| Стратегия | Применение | Поведение |
|---|---|---|
| Cache First | Статика (CSS, JS, изображения) | Отдаёт из кэша, обновляет фоном |
| Network First | HTML-страницы | Сначала сеть, при ошибке — кэш |
| Stale While Revalidate | Изображения, API | Мгновенно из кэша, потом обновляет |
Web App Manifest — основа устанавливаемости
Для появления кнопки «Добавить на главный экран» требуется корректный манифест. Мы настраиваем все поля: name, short_name, icons в 8 размерах (включая maskable), screenshots для Android, shortcuts для быстрых действий. Пример манифеста:
{ "name": "ТехноМагазин — купить электронику", "short_name": "ТехноМагазин", "description": "Смартфоны, ноутбуки, аксессуары с доставкой", "start_url": "/?source=pwa", "scope": "/", "display": "standalone", "orientation": "portrait-primary", "theme_color": "#1a73e8", "background_color": "#ffffff", "lang": "ru", "dir": "ltr", "icons": [ { "src": "/icons/icon-72.png", "sizes": "72x72", "type": "image/png" }, { "src": "/icons/icon-96.png", "sizes": "96x96", "type": "image/png" }, { "src": "/icons/icon-128.png", "sizes": "128x128", "type": "image/png" }, { "src": "/icons/icon-144.png", "sizes": "144x144", "type": "image/png" }, { "src": "/icons/icon-152.png", "sizes": "152x152", "type": "image/png" }, { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" }, { "src": "/icons/icon-384.png", "sizes": "384x384", "type": "image/png" }, { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" } ], "screenshots": [ { "src": "/screenshots/mobile-catalog.webp", "sizes": "390x844", "type": "image/webp", "form_factor": "narrow", "label": "Каталог товаров" } ], "shortcuts": [ { "name": "Корзина", "url": "/cart", "icons": [{ "src": "/icons/cart-96.png", "sizes": "96x96" }] }, { "name": "Избранное", "url": "/wishlist", "icons": [{ "src": "/icons/heart-96.png", "sizes": "96x96" }] } ], "share_target": { "action": "/share", "method": "POST", "enctype": "multipart/form-data", "params": { "title": "title", "text": "text", "url": "url" } } } <link rel="manifest" href="/manifest.json"> <meta name="theme-color" content="#1a73e8"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="default"> <meta name="apple-mobile-web-app-title" content="ТехноМагазин"> <link rel="apple-touch-icon" href="/icons/icon-192.png"> Дополнительно: рекомендуем прочитать документацию Web App Manifest для глубокого понимания.
Почему Service Worker — основа PWA?
Service Worker — это прокси между браузером и сетью. Он перехватывает запросы и решает, отдавать ли кэш, слать запрос или показывать офлайн-страницу. Без него PWA не существует. Мы реализуем стратегию «Cache First для статики, Network First для HTML, Stale While Revalidate для медиа». Пример базового Service Worker:
// sw.js — базовая стратегия для PWA const CACHE_VERSION = 'v3'; const APP_SHELL = [ '/', '/manifest.json', '/offline.html', '/css/app.css', '/js/app.js', '/fonts/inter-regular.woff2', '/icons/icon-192.png', ]; // Установка: кешируем App Shell self.addEventListener('install', event => { event.waitUntil( caches.open(`shell-${CACHE_VERSION}`) .then(cache => cache.addAll(APP_SHELL)) .then(() => self.skipWaiting()) ); }); // Активация: удаляем старые кеши self.addEventListener('activate', event => { event.waitUntil( caches.keys() .then(keys => Promise.all( keys.filter(k => !k.endsWith(CACHE_VERSION)) .map(k => caches.delete(k)) )) .then(() => self.clients.claim()) ); }); // Fetch: разные стратегии для разных ресурсов self.addEventListener('fetch', event => { const { request } = event; const url = new URL(request.url); // App Shell — cache first if (APP_SHELL.includes(url.pathname)) { event.respondWith( caches.match(request).then(r => r || fetch(request)) ); return; } // HTML-страницы — network first, fallback offline if (request.headers.get('Accept')?.includes('text/html')) { event.respondWith( fetch(request) .then(response => { const clone = response.clone(); caches.open(`pages-${CACHE_VERSION}`) .then(cache => cache.put(request, clone)); return response; }) .catch(() => caches.match(request) .then(cached => cached || caches.match('/offline.html')) ) ); return; } // Изображения — stale while revalidate if (request.destination === 'image') { event.respondWith( caches.open(`images-${CACHE_VERSION}`).then(async cache => { const cached = await cache.match(request); const fetchPromise = fetch(request).then(response => { cache.put(request, response.clone()); return response; }); return cached ?? fetchPromise; }) ); } }); Подробнее о Service Worker читайте в справочнике MDN.
Как добавить кнопку установки на сайт?
Чтобы пользователь мог установить PWA, нужно обработать событие beforeinstallprompt. Мы реализуем React-хук useInstallPrompt:
// useInstallPrompt.ts export function useInstallPrompt() { const [installPrompt, setInstallPrompt] = useState<BeforeInstallPromptEvent | null>(null); const [isInstalled, setIsInstalled] = useState(false); useEffect(() => { const handler = (e: BeforeInstallPromptEvent) => { e.preventDefault(); setInstallPrompt(e); }; window.addEventListener('beforeinstallprompt', handler as EventListener); window.addEventListener('appinstalled', () => setIsInstalled(true)); // Проверить — уже установлено? if (window.matchMedia('(display-mode: standalone)').matches) { setIsInstalled(true); } return () => window.removeEventListener('beforeinstallprompt', handler as EventListener); }, []); const install = async () => { if (!installPrompt) return; const result = await installPrompt.prompt(); if (result.outcome === 'accepted') { setIsInstalled(true); setInstallPrompt(null); } }; return { canInstall: !!installPrompt && !isInstalled, install, isInstalled }; } // Использование в компоненте function InstallBanner() { const { canInstall, install } = useInstallPrompt(); if (!canInstall) return null; return ( <div className="install-banner"> <p>Установите приложение для быстрого доступа</p> <button onClick={install}>Установить</button> </div> ); } Пример offline-страницы
<!-- /offline.html --> <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Нет соединения — ТехноМагазин</title> <style> body { font-family: system-ui; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; } .offline { text-align: center; } </style> </head> <body> <div class="offline"> <svg><!-- иконка wifi off --></svg> <h1>Нет соединения</h1> <p>Проверьте подключение к интернету и обновите страницу</p> <button onclick="location.reload()">Попробовать снова</button> </div> </body> </html> Почему PWA не устанавливается: типичные ошибки
Даже при корректном манифесте и Service Worker установка может не сработать. Частая причина — отсутствие HTTPS или неправильные иконки. На iOS требуется явно указать apple-mobile-web-app-capable и добавить иконки нужных размеров. Ещё одна проблема: браузер отклоняет установку, если сайт не соответствует критериям качества (Lighthouse PWA audit). Мы проверяем все эти моменты: тестируем на Chrome, Safari, Firefox, Android и iOS. Если push-уведомления не работают, скорее всего, не настроен VAPID-ключ или сертификат. Все эти нюансы мы отрабатываем на этапе тестирования.
Что входит в работу
- Web App Manifest — полная конфигурация с иконками, сплэшами, шорткатами.
- Service Worker — регистрация, стратегии кэширования, обновление версий.
- Offline-страница — адаптивная, с кнопкой перезагрузки.
- Install Prompt — кнопка установки на десктоп и мобильные.
- Push-уведомления — опционально, интеграция с Firebase или сторонним сервисом.
- Аудит Lighthouse — гарантируем зелёные проверки PWA раздела.
- Документация — описание архитектуры кэширования и инструкция по обновлению.
Сроки разработки
| Этап | Время |
|---|---|
| Манифест и мета-теги | 0.5 дня |
| Service Worker + стратегии | 1–1.5 дня |
| Offline-страница | 0.5 дня |
| Install Prompt и тестирование | 0.5 дня |
| Push-уведомления (опционально) | +1–2 дня |
Итоговый срок: от 2 до 4 дней без push, до 6 дней с ними. Стоимость рассчитывается индивидуально — пишите, оценим ваш проект.
Как мы работаем
- Аналитика — изучаем структуру сайта, определяем App Shell.
- Проектирование — выбираем стратегии кэширования под ваш контент.
- Реализация — пишем Service Worker, настраиваем манифест, интегрируем хуки.
- Тестирование — проверяем на Chrome, Safari, Firefox, Android и iOS.
- Деплой — загружаем на продакшен, мониторим через Lighthouse.
Наша команда имеет 5+ лет опыта в PWA. За это время реализовали проекты для интернет-магазинов, медиа и SaaS. Гарантируем поддержку актуальных стандартов и совместимость с последними версиями браузеров. Свяжитесь с нами, чтобы получить консультацию — расскажем, как PWA улучшит ваш бизнес. Закажите внедрение PWA уже сегодня.







