The Progressive Web App (PWA) stands out as a beacon of hope for delivering a high-quality user experience, akin to native apps, but with the reach and accessibility of the web. Today, we’re going to share some hard-earned lessons from the front lines of building a PWA, focusing on browser and device support, caching strategies, and offline access.

GraphQL

Browser and Device Support: A Balancing Act

When we embarked on our PWA journey, we quickly learned that not all browsers and devices are created equal. While Chrome, Firefox on Android, and Safari on iOS offer robust support for PWA features, others lag behind or implement features differently. For instance, Safari on iOS doesn’t support background sync, and Firefox for desktop has its own set of limitations.

The key lesson here? Test, test, and test again. Use tools like Can I use to check feature support and be prepared to offer fallbacks or alternative instructions for users on less cooperative platforms. Remember, a PWA should be progressive, enhancing the experience where possible, but still functional at its core for everyone.

Caching: The Heartbeat of PWAs

Caching is the secret sauce that allows PWAs to load quickly and function offline. We’ve experimented with various caching strategies, from the straightforward cache-first approach for static assets to the more dynamic network-first strategy for content that updates frequently.

Here’s a simple service worker snippet that employs a cache-first strategy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(cachedResponse => {
        if (cachedResponse) {
          return cachedResponse;
        }
        return fetch(event.request).then(response => {
          if (!response || response.status !== 200 || response.type !== 'basic') {
            return response;
          }
          const responseToCache = response.clone();
          caches.open('my-cache-name')
            .then(cache => {
              cache.put(event.request, responseToCache);
            });
          return response;
        });
      })
  );
});

This code checks the cache first and uses the network as a fallback. It’s a solid strategy for assets that don’t change often, ensuring quick load times and a reliable offline experience.

Offline Access: More Than Just Caching

Offline access isn’t just about caching static files; it’s about ensuring your app remains useful without a network connection. Service workers are the linchpins of offline functionality, allowing you to intercept network requests and serve cached responses.

To provide a seamless offline experience, we also leverage IndexedDB for more complex or dynamic data. Here’s a basic example of using IndexedDB in a service worker:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
self.addEventListener('fetch', event => {
  event.respondWith(
    fetch(event.request).catch(() => {
      // If it fails, try to get the resource from the cache
      return caches.match(event.request).then(response => {
        if (response) {
          return response;
        }
        // If not in the cache, try to get it from IndexedDB
        return myIndexedDB.get(event.request.url).then(data => {
          return new Response(data);
        });
      });
    })
  );
});

This code attempts to fetch from the network, falls back to the cache, and finally tries IndexedDB if the resource isn’t found elsewhere.

wrapping up

The lessons we’ve learned have made our app more resilient and taught us the value of progressive enhancement and the importance of a good fallback plans. If you’re considering building a PWA, remember that it’s not just about the cool offline capabilities or the snappy load times—it’s about providing a consistent and reliable user experience, no matter the conditions. Dive in, experiment, and don’t be afraid to get your hands dirty in the trenches of PWA development.