PWA Background Sync Implementation

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Background Sync Implementation for PWA

Background Sync allows Service Worker to perform deferred action (send form, sync data) on reconnect — even if user closed tab.

How Background Sync works

  1. User performs action (adds to cart) — no internet
  2. App saves task in IndexedDB and registers sync tag
  3. Browser waits for network connection
  4. Service Worker gets sync event and performs deferred task
  5. On failure — browser retries with exponential backoff

Register sync from page

// background-sync.ts
type SyncAction = {
    type: 'cart' | 'wishlist' | 'form' | 'review';
    payload: Record<string, unknown>;
    createdAt: number;
};

async function queueAction(action: SyncAction): Promise<void> {
    // 1. Save in IndexedDB
    const db = await openDatabase();
    await db.put('syncQueue', { ...action, id: Date.now() });

    // 2. Register Background Sync
    const registration = await navigator.serviceWorker.ready;

    if ('sync' in registration) {
        await (registration as any).sync.register(`sync-${action.type}`);
    } else {
        // Fallback for browsers without Background Sync
        if (navigator.onLine) {
            await processAction(action);
        }
    }
}

// Open IndexedDB
async function openDatabase(): Promise<IDBDatabase> {
    return new Promise((resolve, reject) => {
        const request = indexedDB.open('PWASync', 1);
        request.onupgradeneeded = e => {
            (e.target as IDBOpenDBRequest).result
                .createObjectStore('syncQueue', { keyPath: 'id' });
        };
        request.onsuccess = e => resolve((e.target as IDBOpenDBRequest).result);
        request.onerror = reject;
    });
}

Service Worker: handle sync events

// sw.js
self.addEventListener('sync', event => {
    console.log('Background sync triggered:', event.tag);

    switch (event.tag) {
        case 'sync-cart':
            event.waitUntil(syncCart());
            break;
        case 'sync-wishlist':
            event.waitUntil(syncWishlist());
            break;
        case 'sync-form':
            event.waitUntil(syncPendingForms());
            break;
        case 'sync-review':
            event.waitUntil(syncPendingReviews());
            break;
    }
});

async function syncCart() {
    const db = await openIDB('PWASync', 1);
    const tx = db.transaction('syncQueue', 'readwrite');
    const store = tx.objectStore('syncQueue');

    const actions = await getAllFromStore(store, 'cart');

    for (const action of actions) {
        const response = await fetch('/api/cart/items', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Sync': 'background',
            },
            body: JSON.stringify(action.payload),
        });

        if (response.ok) {
            await store.delete(action.id);
            // Notify open tabs about sync
            const clients = await self.clients.matchAll();
            clients.forEach(client => {
                client.postMessage({ type: 'CART_SYNCED', payload: action.payload });
            });
        } else if (response.status >= 400 && response.status < 500) {
            // Client error — delete, don't retry
            await store.delete(action.id);
        }
        // 5xx — leave for retry (browser retries sync automatically)
    }
}

Periodic Background Sync (Chrome)

Allows tasks on schedule — update exchange rates, news, weather:

// Register
async function registerPeriodicSync() {
    const registration = await navigator.serviceWorker.ready;

    if ('periodicSync' in registration) {
        const status = await navigator.permissions.query({ name: 'periodic-background-sync' as any });

        if (status.state === 'granted') {
            await (registration as any).periodicSync.register('update-prices', {
                minInterval: 60 * 60 * 1000, // not more than hourly
            });
        }
    }
}
// sw.js: periodic sync
self.addEventListener('periodicsync', event => {
    if (event.tag === 'update-prices') {
        event.waitUntil(updateCachedPrices());
    }
});

async function updateCachedPrices() {
    const response = await fetch('/api/prices/current');
    const prices = await response.json();

    const cache = await caches.open('dynamic-v1');
    // Update cached data
    await cache.put('/api/prices/current', new Response(JSON.stringify(prices), {
        headers: { 'Content-Type': 'application/json' }
    }));

    // Notify if wishlist price changed
    await checkWishlistPriceChanges(prices);
}

Display sync status

// useSyncStatus.ts
export function useSyncStatus() {
    const [pendingCount, setPendingCount] = useState(0);
    const [isSyncing, setIsSyncing] = useState(false);

    useEffect(() => {
        // Listen Service Worker messages
        const handler = (event: MessageEvent) => {
            if (event.data.type === 'CART_SYNCED') {
                setPendingCount(c => Math.max(0, c - 1));
                setIsSyncing(false);
            }
            if (event.data.type === 'SYNC_STARTED') {
                setIsSyncing(true);
            }
        };

        navigator.serviceWorker.addEventListener('message', handler);
        return () => navigator.serviceWorker.removeEventListener('message', handler);
    }, []);

    return { pendingCount, isSyncing };
}

// In header component
function SyncIndicator() {
    const { pendingCount, isSyncing } = useSyncStatus();

    if (pendingCount === 0) return null;

    return (
        <div className="sync-indicator">
            {isSyncing ? 'Syncing...' : `${pendingCount} actions waiting for sync`}
        </div>
    );
}

Implementation time: 1–2 days for basic Background Sync with cart and forms.