Web Push уведомления: полное внедрение и оптимизация

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Web Push уведомления: полное внедрение и оптимизация
Средний
~2-3 дня
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1358
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1251
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    957
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1188
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    947

Техническая интеграция Web Push — задача, которая кажется простой, пока не столкнёшься с нюансами VAPID, истечением подписки или блокировками браузеров. Мы внедряли этот механизм для 30+ проектов: от интернет-магазинов с миллионной аудиторией до стартапов с десятком пользователей. И каждый раз находили неочевидные грабли — например, Safari на iOS до версии 16.4 просто игнорирует push, а Chrome может принудительно удалять подписки без уведомления. В этом материале разберём реальную реализацию: от генерации VAPID-ключей до серверной отправки на Laravel и мониторинга доставки.

Согласно спецификации Push API (MDN): "Service Worker обязан обрабатывать событие push для отображения уведомления"

Проблемы, которые решаем

Несовместимость браузеров — не единственная сложность. Push-уведомления в браузере живут по строгим правилам: нельзя отправить сообщение без явного разрешения, и оно легко теряется. Вот основные болевые точки:

  • Истечение подписки: браузер автоматически удаляет неактивные эндпоинты. Без регулярной чистки вы будете тратить ресурсы на мёртвые подписки — до 30% базы может быть невалидной.
  • Персонализация: типовые уведомления без сегментации дают CTR ниже 10%. Сегментация по действиям (брошенная корзина, возврат, достижение) поднимает отклик в три раза — с 8% до 24%.
  • Ошибки VAPID: неверный формат ключа или просроченный subject приводят к молчаливому отказу Push Service. Из-за этого теряется до 15% отправок.

Почему VAPID строго обязателен? Это единственный способ аутентификации сервера перед Push Service. Без него запросы просто игнорируются. Генерируем ключи один раз — приватный хранится в .env, публичный передаётся браузеру при подписке.

Как работает архитектура

Ваш сервер → Push Service (Google FCM, Mozilla Autopush) → Браузер → Service Worker → Уведомление

Service Worker — это прокси между сервером и пользователем. Он работает даже когда сайт закрыт, обрабатывает события push и отображает уведомления. Критично правильно реализовать его регистрацию и обработку кликов.

Сравнение каналов коммуникации

Канал CTR Скорость доставки Персонализация Стоимость
Email ~20% Минуты-часы Высокая Низкая
SMS ~30% Секунды Средняя Высокая
Web Push ~60% Секунды Высокая Низкая

Web Push выигрывает по трём параметрам: дешевле SMS (экономия до $0,02 на сообщение), быстрее Email и персонализируется без потери скорости. Теперь к реализации.

Как сегментация повышает CTR?

Сегментация по событиям продукта даёт прирост CTR в 2-3 раза. Пример: интернет-магазин, внедрив персонализированные push о статусе заказа, увеличил повторные визиты на 30%, а средний чек — на 15%. Используйте теги: для брошенной корзины — cart-abandoned, для новостей — news, для акций — promo. Так вы не перегружаете пользователя и повышаете релевантность.

Что делать при ошибках доставки?

Ошибки Push Service возвращают коды: 410 (подписка удалена), 404 (не найдена), 429 (лимит). В коде на Laravel (см. раздел Сервер) мы автоматически удаляем мёртвые эндпоинты после получения 410. Это снижает количество неудачных отправок на 20%. Если уведомления не доходят, проверьте VAPID-ключи и формат эндпоинта.

Пошаговая инструкция внедрения

  1. Генерация VAPID-ключей: npx web-push generate-vapid-keys, сохраните приватный ключ в .env.
  2. Регистрация Service Worker на фронте: в отдельном файле sw.js обрабатываем события push и notificationclick.
  3. Подписка браузера: используем PushManager.subscribe() с VAPID public key. Отправляем объект подписки на сервер.
  4. Серверное хранение: сохраняем эндпоинт, p256dh и auth токен в базу (например, PostgreSQL).
  5. Настройка Push API: формируем payload (title, body, icon, url) и отправляем через библиотеку minishlink/web-push.
  6. Обработка результатов: удаляем эндпоинты, вернувшие ошибку 410 или 404.
Подробнее про VAPID VAPID (Voluntary Application Server Identification) — стандарт IETF RFC 8292. Приватный ключ подписывает токен, который браузер передаёт Push Service. Если сервер не предоставляет корректного токена, Push Service отклоняет запрос. Ключи можно генерировать через `web-push` или руками, используя Elliptic Curve P-256.

Код подписки на TypeScript

// push-subscription.ts
const VAPID_PUBLIC_KEY = import.meta.env.VITE_VAPID_PUBLIC_KEY;

function urlBase64ToUint8Array(base64String: string): Uint8Array {
    const padding = '='.repeat((4 - base64String.length % 4) % 4);
    const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
    const rawData = window.atob(base64);
    return Uint8Array.from([...rawData].map(char => char.charCodeAt(0)));
}

export async function subscribeToPush(): Promise<boolean> {
    if (!('PushManager' in window)) {
        console.warn('Push notifications not supported');
        return false;
    }

    const permission = await Notification.requestPermission();
    if (permission !== 'granted') return false;

    const registration = await navigator.serviceWorker.ready;
    const subscription = await registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
    });

    await fetch('/api/push/subscribe', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(subscription),
    });

    return true;
}

export async function unsubscribeFromPush(): Promise<void> {
    const registration = await navigator.serviceWorker.ready;
    const subscription = await registration.pushManager.getSubscription();
    if (subscription) {
        await subscription.unsubscribe();
        await fetch('/api/push/unsubscribe', {
            method: 'DELETE',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ endpoint: subscription.endpoint }),
        });
    }
}

Service Worker: обработка push и кликов

// sw.js
self.addEventListener('push', event => {
    const data = event.data?.json() ?? {};
    const options = {
        body:    data.body ?? 'Новое уведомление',
        icon:    data.icon ?? '/icons/icon-192.png',
        badge:   '/icons/badge-72.png',
        image:   data.image,
        tag:     data.tag ?? 'default',
        renotify: data.renotify ?? false,
        data:    { url: data.url ?? '/' },
        actions: data.actions ?? [],
        requireInteraction: data.requireInteraction ?? false,
    };
    event.waitUntil(
        self.registration.showNotification(data.title ?? 'Уведомление', options)
    );
});

self.addEventListener('notificationclick', event => {
    event.notification.close();
    const url = event.notification.data?.url ?? '/';
    event.waitUntil(
        clients.matchAll({ type: 'window', includeUncontrolled: true })
            .then(windowClients => {
                const existing = windowClients.find(c => c.url === url && 'focus' in c);
                if (existing) return existing.focus();
                return clients.openWindow(url);
            })
    );
});

Сервер на Laravel: подписка, отправка и очистка

// Миграция таблицы push_subscriptions
Schema::create('push_subscriptions', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
    $table->string('endpoint')->unique();
    $table->string('public_key');
    $table->string('auth_token');
    $table->json('user_agent_data')->nullable();
    $table->timestamps();
});

// Контроллер подписки
class PushSubscriptionController extends Controller
{
    public function subscribe(Request $request): JsonResponse
    {
        $data = $request->validate([
            'endpoint'        => 'required|url',
            'keys.p256dh'     => 'required|string',
            'keys.auth'       => 'required|string',
        ]);

        PushSubscription::updateOrCreate(
            ['endpoint' => $data['endpoint']],
            [
                'user_id'    => auth()->id(),
                'public_key' => $data['keys']['p256dh'],
                'auth_token' => $data['keys']['auth'],
            ]
        );

        return response()->json(['status' => 'ok']);
    }
}

// Класс отправки уведомлений
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;

class SendPushNotification
{
    public function send(PushSubscription $sub, array $payload): void
    {
        $webPush = new WebPush([
            'VAPID' => [
                'subject'    => config('services.vapid.subject'),
                'publicKey'  => config('services.vapid.public_key'),
                'privateKey' => config('services.vapid.private_key'),
            ],
        ]);

        $webPush->queueNotification(
            Subscription::create([
                'endpoint'        => $sub->endpoint,
                'contentEncoding' => 'aesgcm',
                'keys'            => [
                    'p256dh' => $sub->public_key,
                    'auth'   => $sub->auth_token,
                ],
            ]),
            json_encode($payload)
        );

        foreach ($webPush->flush() as $report) {
            if (!$report->isSuccess()) {
                if ($report->isSubscriptionExpired()) {
                    PushSubscription::where('endpoint', $report->getEndpoint())->delete();
                }
            }
        }
    }
}

Сценарий: уведомления о статусе заказа

Пользователь оформил заказ — система автоматически отправляет push: "Заказ #123: статус изменён на 'Отгружен'". Реализуется через событие OrderStatusChanged. Для этого создаём seeder или слушатель, который при изменении статуса заказа собирает подписки пользователя и отправляет через SendPushNotification.

class OrderStatusChanged
{
    public function handle(Order $order): void
    {
        $user = $order->user;
        $subscriptions = PushSubscription::where('user_id', $user->id)->get();

        foreach ($subscriptions as $sub) {
            $this->sender->send($sub, [
                'title'   => 'Статус заказа изменён',
                'body'    => "Заказ #{$order->number}: {$order->status_label}",
                'icon'    => '/icons/order-icon.png',
                'url'     => "/account/orders/{$order->id}",
                'tag'     => "order-{$order->id}",
                'renotify' => true,
                'actions' => [
                    ['action' => 'view',   'title' => 'Посмотреть заказ'],
                    ['action' => 'dismiss', 'title' => 'Закрыть'],
                ],
            ]);
        }
    }
}

Что вы получаете

  • Полноценный код Service Worker и клиентской подписки на TypeScript.
  • Серверную часть на Laravel с миграциями, контроллером и классом отправки.
  • Настроенную аналитику доставки и автоматическую очистку мёртвых подписок.
  • Документацию: описание API, инструкцию для администратора, доступ к логам.
  • Поддержку в течение 2 недель после внедрения.

Этапы и сроки внедрения

Этап Длительность Результат
Аудит текущей архитектуры 1 день План интеграции с учётом стека
Настройка VAPID и Service Worker 0.5 дня Готовый SW с обработкой push
Реализация подписки (фронт+бэк) 1–2 дня Рабочая подписка и отписка
Настройка сценариев отправки 1 день Уведомления по событиям (заказ, регистрация)
Тест и мониторинг 0.5 дня Отчёт о доставке + логи ошибок

Сроки: от 1 до 3 дней в зависимости от сложности сценариев.

По статистике, правильно настроенные push-уведомления повышают повторные визиты на 30%, а средний чек — на 15%. Если хотите проверить, как это работает на вашем проекте, пришлите ссылку — мы за один день подготовим оценку. Для консультации свяжитесь с нами.

Для справки: MDN Web Push API — официальная документация.

Когда приложение не открывается без интернета, пользователь уходит к конкурентам

PWA превращает сайт в надёжное приложение, которое работает даже в офлайне, отправляет push-уведомления и устанавливается на главный экран. Разработка PWA приложений — это внедрение Service Worker, настройка кэш-стратегий и интеграция Web Push. В отличие от нативных приложений, вам не нужны две команды под iOS и Android — один код работает во всех современных браузерах. Закажите аудит вашего проекта на PWA-совместимость — мы бесплатно оценим потенциал и сроки.

Как Service Worker управляет сетевыми запросами?

Service Worker — это JavaScript-прокси между браузером и сетью. Работает в отдельном потоке, перехватывает запросы и решает, откуда их отдавать: из кэша, из сети или комбинацией. Разберём три базовые стратегии на реальных сценариях.

Стратегия кэширования Использование Поведение при офлайн
Cache First статика (CSS/JS с content hash) отдаётся из кэша, мгновенно
Network First API, заказы, новости сначала сеть, при ошибке — кэш
Stale While Revalidate контент соцсетей, лента страниц сразу кэш, потом обновление

Cache First применяется для ассетов с хешем в имени — файл никогда не изменится, можно кэшировать навсегда. Stale While Revalidate оптимальна для контента, где допустима небольшая задержка актуализации. Workbox от Google автоматизирует версионирование кэша и инвалидацию — без него корректный Service Worker потребует 300+ строк кода с нетривиальными edge cases. Vite + vite-plugin-pwa генерирует Service Worker из конфига, включая precaching статики.

import { VitePWA } from 'vite-plugin-pwa';
export default {
  plugins: [
    VitePWA({
      registerType: 'autoUpdate',
      includeAssets: ['favicon.ico'],
      manifest: { /* name, icons, start_url, display */ },
      workbox: {
        globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
        runtimeCaching: [
          { urlPattern: /^https?:\/\/api\./,
            handler: 'NetworkFirst',
            options: { cacheName: 'api-cache' }
          }
        ]
      }
    })
  ]
};

Как офлайн-режим реализуется на практике?

«Работает офлайн» для разных продуктов означает разное. Вот три типовых сценария.

  • Офлайн-чтение (новостные сайты, документация): Service Worker кэширует страницы при первом визите, стратегия Stale While Revalidate + Background Sync для синхронизации после восстановления соединения.
  • Офлайн-редактирование (заметки, задачи): IndexedDB хранит локальные данные, Background Sync API ставит операции в очередь — браузер синхронизирует сам, даже если вкладка закрыта. Ограничение: Background Sync поддерживается только в Chromium.
  • Офлайн-форма: пользователь нажал «Отправить» без интернета — данные не теряются, а ставятся в очередь и отправляются автоматически. Для медицинских и страховых форм это критично.

Проблема, о которой часто забывают: конфликты при синхронизации. Если пользователь А редактировал запись офлайн, а пользователь Б изменил её онлайн — нужна стратегия разрешения (last-write-wins, three-way merge или показ конфликта пользователю). Мы прорабатываем эти сценарии на этапе проектирования.

Как работают Web Push-уведомления?

Web Push доставляет сообщения через браузер + Push Service (FCM для Chrome/Edge, APNs для Safari). Пользователь даёт разрешение → браузер подписывается на Push Service → вы получаете endpoint и ключи → отправляете сообщение → Push Service доставляет в браузер. Реализация через библиотеку web-push (Node.js) или аналог для вашего бэкенда. VAPID-ключи генерируются один раз, подписка хранится в базе данных.

На текущий момент iOS (начиная с версии 16.4) поддерживает Web Push только для установленных PWA, Chrome/Firefox/Edge — полная поддержка без установки. Частота и релевантность уведомлений напрямую влияют на отток подписчиков — A/B тестирование времени отправки и формулировок стандартная практика.

Почему стоит выбрать PWA вместо нативных приложений?

Экономия на разработке под две платформы — до 60% бюджета. Один код, единая бизнес-логика, автоматическое обновление без магазинов. Мы выполнили более 30 PWA-проектов для e-commerce, финтеха и корпоративных систем — гарантируем совместимость с последними версиями браузеров и отличные показатели Core Web Vitals (LCP, CLS, INP). PWA увеличивает конверсию в среднем на 36% (данные Google). Сертифицированные специалисты с опытом 7+ лет в веб-разработке.

Что входит в разработку PWA под ключ?

Этап Результат Срок
Аудит текущего приложения Отчёт PWA‑score, рекомендации 1–2 дня
Проектирование офлайн‑сценариев Документация, прототип 2–3 дня
Разработка Service Worker + манифест Код, автоматическое тестирование 5–10 дней
Интеграция Web Push (опционально) Бэкенд‑ендпоинт, подписка 3–5 дней
Тестирование на реальных устройствах Отчёт, правки 3–5 дней
Деплой и документация Доступы, инструкция, гарантия 1 месяц 1–2 дня

App Shell архитектура и precaching критических ресурсов при первой установке дают мгновенную загрузку оболочки приложения даже при медленном соединении.

Процесс работы и сроки

  1. Аудит текущего приложения (Lighthouse PWA score, анализ сценариев).
  2. Определение ценных офлайн-сценариев.
  3. Настройка Service Worker через Workbox и реализация манифеста.
  4. Интеграция Web Push (если требуется).
  5. Тестирование на реальных устройствах — Chrome DevTools, Safari Web Inspector.
  6. Деплой, документация, обучение команды.

Ориентировочные сроки: базовая PWA (манифест + Service Worker + кэш статики) — 1–2 недели поверх готового приложения; Web Push — 1–2 недели; офлайн-редактирование с IndexedDB и Background Sync — 3–6 недель в зависимости от сложности данных.

Стоимость рассчитывается индивидуально после аудита. PWA-проект обычно обходится в 2–3 раза дешевле нативного приложения под обе платформы. Дополнительную экономию даёт отсутствие затрат на публикацию в App Store и Google Play. Свяжитесь с нами для консультации — мы бесплатно оценим ваш проект и предложим оптимальную стратегию внедрения.

Ссылки для углублённого изучения