← Back to Articles

Progressive Web Apps: Building Offline-First Applications

Code

Progressive Web Apps (PWAs) have changed how we think about web applications. They're not just websites—they're web applications that can work offline, send push notifications, and even be installed on devices like native apps. In this article, I'll show you how to build an offline-first PWA.

What makes an app "progressive"?

A Progressive Web App is a web application that uses modern web capabilities to provide a user experience similar to native apps. The key characteristics of PWAs are:

  • Responsive: Works on any device and screen size
  • Offline-capable: Works without an internet connection
  • Installable: Can be added to the home screen
  • Safe: Served over HTTPS
  • Engagement: Push notifications and background sync

Service Workers: The heart of PWAs

Service workers are the technology that makes PWAs possible. They're JavaScript files that run in the background, separate from your main application. Service workers can:

  • Intercept network requests
  • Cache resources
  • Handle push notifications
  • Perform background sync

Let's start with a basic service worker setup:

// public/sw.js
const CACHE_NAME = 'my-pwa-v1';
const urlsToCache = [
  '/',
  '/static/css/main.css',
  '/static/js/main.js',
  '/manifest.json'
];

// Install event - cache resources
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(urlsToCache))
  );
});

// Fetch event - serve from cache when offline
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // Return cached version or fetch from network
        return response || fetch(event.request);
      })
  );
});

Caching strategies

There are different strategies for caching resources:

  • Cache First: Check cache first, then network
  • Network First: Check network first, then cache
  • Stale While Revalidate: Serve cached version immediately, then update in background
  • Cache Only: Only serve from cache

For an offline-first approach, cache-first is usually best:

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        if (response) {
          return response; // Return cached version
        }

        return fetch(event.request).then(response => {
          // Don't cache non-GET requests
          if (!response || response.status !== 200 || response.type !== 'basic') {
            return response;
          }

          const responseToCache = response.clone();
          caches.open(CACHE_NAME)
            .then(cache => cache.put(event.request, responseToCache));

          return response;
        });
      })
  );
});

The Web App Manifest

To make your PWA installable, you need a web app manifest. This JSON file tells the browser how your app should behave when installed:

{
  "name": "My Progressive Web App",
  "short_name": "MyPWA",
  "description": "A progressive web application",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#000000",
  "icons": [
    {
      "src": "/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Offline UI and user experience

When your app is offline, you need to provide appropriate feedback to users. Create an offline page or show a message:

// Check online status
function updateOnlineStatus() {
  const statusElement = document.getElementById('status');
  if (navigator.onLine) {
    statusElement.textContent = 'Online';
    statusElement.className = 'online';
  } else {
    statusElement.textContent = 'Offline';
    statusElement.className = 'offline';
  }
}

window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);

Handling form submissions offline

One of the powerful features of PWAs is background sync. You can queue actions when offline and sync them when the connection returns:

// Register for background sync
if ('serviceWorker' in navigator && 'sync' in window.ServiceWorkerRegistration.prototype) {
  navigator.serviceWorker.ready.then(registration => {
    // Register sync when user submits a form
    document.getElementById('myForm').addEventListener('submit', event => {
      event.preventDefault();

      registration.sync.register('form-submit')
        .then(() => console.log('Sync registered'))
        .catch(err => console.error('Sync registration failed', err));
    });
  });
}

// In service worker
self.addEventListener('sync', event => {
  if (event.tag === 'form-submit') {
    event.waitUntil(doFormSubmit());
  }
});

Testing your PWA

Use these tools to test your PWA:

  • Lighthouse: Chrome DevTools audit for PWA features
  • Application panel: Chrome DevTools for inspecting service workers and cache
  • Web App Manifest validator: Check your manifest file

Deployment considerations

When deploying your PWA:

  • Serve over HTTPS
  • Pre-cache critical resources
  • Update service worker version numbers for cache invalidation
  • Test on multiple devices and browsers

PWAs are becoming increasingly important as users expect web apps to work like native apps. With service workers and modern web APIs, you can create applications that provide excellent offline experiences and compete with native applications.

About the author

Rafael De Paz

Full Stack Developer

Passionate full-stack developer specializing in building high-quality web applications and responsive sites. Expert in robust data handling, leveraging modern frameworks, cloud technologies, and AI tools to deliver scalable, high-performance solutions that drive user engagement and business growth. I harness AI technologies to accelerate development, testing, and debugging workflows.

Tags:

Share: