Core Technologies of PWAs: Service Workers, Web App Manifests, and Caching Strategies

To fully implement a PWA, you need to understand its core technologies:

Service Workers

A service worker enables background processes like caching and push notifications. The following example demonstrates a cache-first strategy:

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cachedResponse) => {
      return cachedResponse || fetch(event.request);
    })
  );
});

This ensures that previously loaded resources are served from cache first, reducing network latency.

Web App Manifest

The manifest defines how the app should appear and behave. A sample manifest.json:

{
  "name": "Rails PWA",
  "short_name": "RailsPWA",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#000000",
  "icons": [
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Caching Strategies

Common caching strategies include:

Implementing these strategies ensures fast load times and offline capabilities.

Example: Implement Caching Strategies in Service Worker

Create a service_worker.js file and define different caching strategies.

const CACHE_NAME = 'rails-pwa-cache-v1';
const ASSETS_TO_CACHE = [
  '/',
  '/offline.html', 
  '/assets/application.css',
  '/assets/application.js'
];

// Install Event - Cache assets
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => {
      return cache.addAll(ASSETS_TO_CACHE);
    })
  );
});

// Activate Event - Cleanup old caches
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cache => {
          if (cache !== CACHE_NAME) {
            return caches.delete(cache);
          }
        })
      );
    })
  );
});

// Fetch Event - Implement caching strategies
self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);

  // Cache-First Strategy (for static assets)
  if (url.origin === location.origin && ASSETS_TO_CACHE.includes(url.pathname)) {
    event.respondWith(
      caches.match(event.request).then(response => {
        return response || fetch(event.request);
      })
    );
    return;
  }

  // Network-First Strategy (for API calls)
  if (url.pathname.startsWith('/api/')) {
    event.respondWith(
      fetch(event.request).then(response => {
        return caches.open(CACHE_NAME).then(cache => {
          cache.put(event.request, response.clone());
          return response;
        });
      }).catch(() => caches.match(event.request))
    );
    return;
  }

  // Stale-While-Revalidate Strategy (for dynamic pages)
  event.respondWith(
    caches.match(event.request).then(cachedResponse => {
      const fetchPromise = fetch(event.request).then(networkResponse => {
        caches.open(CACHE_NAME).then(cache => {
          cache.put(event.request, networkResponse.clone());
        });
        return networkResponse;
      });

      return cachedResponse || fetchPromise;
    })
  );
});