Development of a PWA application based on 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1167
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    563
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    743
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

PWA Application Development on 1C-Bitrix

Since version 21.0, Bitrix ships a built-in pwa module — it generates manifest.json, registers a Service Worker, and provides basic offline capability. The demo looks convincing: the site installs to the home screen, works without a network, sends push notifications. In practice, the module handles about 40% of real-world requirements. The rest — caching strategies, interaction with the composite module, iOS workarounds, Lighthouse score tuning — needs custom work.

What the Built-In pwa Module Does

Enable the module at Admin → Settings → Module Settings → PWA. Once active, Bitrix does three things:

  1. Generates /manifest.json from module settings — name, short_name, start_url, display, theme_color, background_color, and an icons[] array
  2. Registers a Service Worker /sw.js via navigator.serviceWorker.register('/sw.js') injected into the site template
  3. Inserts <link rel="manifest" href="/manifest.json"> into <head>

The display parameter defaults to standalone — the app opens without the browser address bar. For sites where users rely on browser back-navigation, switch to minimal-ui.

Icons — the module expects a full set: 72x72, 96x96, 128x128, 144x144, 152x152, 192x192, 384x384, 512x512 in PNG. Missing the 512x512 icon costs you Lighthouse installability points. We generate the set from one source file via CFile::ResizeImageGet(), but check quality manually — automatic downscaling sometimes produces artifacts at small sizes.

Service Worker Caching Strategies — The Core of PWA Behavior

The stock /sw.js from Bitrix applies Cache First for static assets and Network First for HTML pages. This gives you a basic offline shell but falls short of real PWA behavior.

Cache First — the Service Worker checks CacheStorage before hitting the server. Works well for /bitrix/js/, /bitrix/css/, /upload/. The problem: when you deploy a new JS bundle, users keep getting the old version until the cache expires or skipWaiting() triggers. On a site with frequent deploys, this means stale JavaScript for hours.

We solve this with cache versioning. The Service Worker maintains a version constant. On activate, it deletes all caches except the current version:

const CACHE_VERSION = 'v1.4';
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(keys =>
      Promise.all(keys.filter(k => k !== CACHE_VERSION).map(k => caches.delete(k)))
    )
  );
});

Network First — the browser tries the server, falls back to cache if offline. Correct for HTML, but interacts poorly with the composite module. Composite serves pre-rendered HTML from /bitrix/html_pages/ via Nginx — bypassing PHP entirely. When a Service Worker with Cache First for HTML sits on top, it intercepts the response before Nginx even receives the request. If composite updated the page (product sold out, price changed), the user still sees the cached version.

Stale While Revalidate — not used by the stock module, but we add it for API endpoints and catalog data. Serves instantly from cache, updates the cache in the background. Ideal for product listings that change hourly but shouldn't block rendering.

Custom caching logic goes in /local/templates/main/sw-custom.js, imported via importScripts() in the main Service Worker. We never edit /sw.js directly — the module regenerates it.

composite Module Interaction

Both composite and PWA aim to speed up first render, but they work differently and can conflict.

composite creates a static HTML snapshot and stores it in the filesystem (/bitrix/html_pages/). Nginx serves the file directly — response time drops to 5-15ms. Dynamic zones (<div id="bx-composite-...">) load via a separate AJAX call after page load.

The conflict: when Service Worker caches composite HTML, and the composite cache invalidates (because content changed), the Service Worker still serves the old snapshot. The user sees stale data even though the server has fresh content.

Our approach:

  • Use Network First for HTML navigation requests
  • Exclude bitrix/services/main/ajax.php from caching — this endpoint serves dynamic composite zones
  • Skip caching any response that includes a sessid parameter — these are user-specific
  • Version the Service Worker cache and increment on every deployment

Push Notifications via the pull Module

The pull module (Push and Pull) powers Bitrix's real-time features — chat, notifications, live data updates. For PWA, it also serves as the push notification transport.

Architecture: the client subscribes via PushSubscription API → sends endpoint, keys.p256dh, keys.auth to the server → the server stores the subscription in b_pull_push → when an event fires, it sends the payload through \Bitrix\Pull\Push::send().

Setup steps:

  1. Generate VAPID keys: openssl ecparam -genkey -name prime256v1 -out private.pem
  2. Enter the public key in pull module settings → "Push Notifications" → "VAPID Public Key"
  3. Handle the push event in the Service Worker:
self.addEventListener('push', event => {
  const data = event.data.json();
  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: '/local/templates/main/img/icon-192.png',
      data: { url: data.url }
    })
  );
});

Clicking a notification opens the target URL via the notificationclick handler — clients.openWindow(event.notification.data.url).

iOS Limitations

iOS deserves its own section because PWA support in Safari is materially behind Chrome.

  • Web Push — only supported since iOS 16.4, and only for sites added to the Home Screen. PushManager.subscribe() in a regular Safari tab throws an error
  • beforeinstallprompt — not supported. There's no programmatic way to trigger an install prompt. We show a manual instruction: "Tap Share → Add to Home Screen"
  • Background Sync — not supported. If the user submits a form while offline, you can't queue it for later sending through the Sync API. Workaround: store in IndexedDB and retry on online event
  • Badge API — not supported. No way to show an unread count on the app icon
  • Storage quota — Safari limits each origin to approximately 50MB of cache storage vs. Chrome's 80% of free disk space
  • Service Worker lifetime — Safari aggressively kills Service Workers after a few days of inactivity. If the user doesn't open the PWA for a week, push notifications stop until they open it again

For projects targeting iOS users heavily, these limitations should be discussed with the client upfront. A PWA on iOS is closer to a bookmark with offline support than to a native app.

Lighthouse PWA Audit Checklist

Lighthouse evaluates PWA against a specific checklist. On Bitrix, the following require attention:

  • Installable — valid manifest.json, Service Worker with a fetch handler, 512x512 icon, start_url returns 200 offline. The stock module covers this if you upload all icon sizes
  • HTTPS — mandatory. Configure in Bitrix at Settings → Main Module → "Use HTTPS". Ensure Nginx redirects HTTP → HTTPS without a chain (single 301)
  • theme-color<meta name="theme-color"> must match theme_color in manifest.json. The module sets both, but verify they survive template caching
  • Offline fallback — when the network is down, the user should see a custom page, not Chrome's dinosaur. Pre-cache /offline.html during Service Worker install
  • start_url redirectsstart_url must not return 3xx. On multilingual Bitrix sites that redirect //en/, set start_url to /en/ in the manifest

PWA vs Native App

Criterion PWA Native (iOS/Android)
Installation From the browser, no app store App Store / Google Play
Updates Automatic via Service Worker Through the store, user confirmation
Device access Camera, geolocation, notifications Full device API access
Offline Cached content only Full offline logic
Size 0 MB (browser cache) 20-100+ MB
Development cost 1-2 weeks on top of existing site 2-4 months as a separate project

For catalogs, corporate sites, news portals — PWA is sufficient. For apps requiring Bluetooth, NFC, heavy offline computation, or deep OS integration — native is the only option.

Project Stages

  1. Site audit (2-3 days) — check template compatibility with PWA requirements, run Lighthouse, evaluate composite module interaction
  2. Module configuration and Service Worker (3-5 days) — manifest.json setup, caching strategies, offline page, push subscription flow
  3. Cross-platform testing (2-3 days) — Android (Chrome, Samsung Internet), iOS (Safari), desktop (Chrome, Edge). Lighthouse audit at each stage
  4. Deployment and monitoring (1-2 days) — production rollout, metrics verification, Service Worker error alerting
Scope Timeline
PWA for existing site with composite 1-2 weeks
PWA + push notifications + offline catalog 2-3 weeks
PWA with custom App Shell and complex offline logic 3-5 weeks