Building an Offline PWA: A Step-by-Step Guide with Sample Code

Sai Kumar Gandhi
4 min readApr 29, 2023

--

Building offline PWA (Source: freepik.com)

In today’s world, users expect applications to work even when they don’t have an internet connection. Building an offline Progressive Web App (PWA) can provide a seamless experience for users, allowing them to continue using your app even when they are offline. In this article, we’ll walk through the steps of building an offline PWA using service workers and caching.

Getting Started: To get started, let’s set up a basic PWA with a manifest and service worker. The manifest provides metadata about the app and allows it to be installed on the user’s device. The service worker is the backbone of the PWA and enables offline functionality.

First, create a file called manifest.json and add the following content:

{
"name": "My PWA",
"short_name": "PWA",
"theme_color": "#ffffff",
"background_color": "#ffffff",
"icons": [
{
"src": "/images/icon.png",
"sizes": "192x192",
"type": "image/png"
}
],
"start_url": "/"
}

This manifest includes the name and short name of the app, as well as an icon and start URL.

Next, create a file called service-worker.js and add the following content:

self.addEventListener('install', event => {
event.waitUntil(
caches.open('my-cache').then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js',
'/images/logo.png'
]);
})
);
});

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

This service worker caches the app shell (HTML, CSS, JS, and images) when the user first visits the site. It then intercepts network requests and responds with cached content when available.

Caching: Now that we have a basic PWA set up with a service worker, let’s implement caching to provide offline functionality. Caching allows us to store resources locally on the user’s device so they can be accessed even when there is no internet connection.

To implement caching, we’ll use the Cache API. In the install event of the service worker, we'll open a cache and add our app shell files to it:

self.addEventListener('install', event => {
event.waitUntil(
caches.open('my-cache').then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js',
'/images/logo.png'
]);
})
);
});

This code opens a cache called “my-cache” and adds the app shell files to it. When the service worker is installed, these files will be cached.

To serve cached content, we’ll update the fetch event of the service worker to check the cache for a response:

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

This code checks the cache for a response to the current request. If a cached response is found, it is returned. If not, the request is forwarded to the network.

Service Workers: Service workers are a critical component of offline PWAs, as they enable us to intercept network requests and provide cached content. They also allow us to handle cache invalidation and updates.

To implement service workers, we’ll use the navigator.serviceWorker API in JavaScript. First, we'll register the service worker in our main script file:

if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('Service worker registered:', registration);
})
.catch(error => {
console.log('Service worker registration failed:', error);
});
});
}

This code checks if the serviceWorker API is available and registers the service worker by calling navigator.serviceWorker.register(). We can also handle errors if the registration fails.

Now that we have a service worker registered, we can test our app in offline mode. If we disable our internet connection and refresh the page, we should still see our app shell content. This is because the service worker is intercepting requests and serving cached content.

Cache Invalidation: Now that we have caching implemented, we need to handle cache invalidation to ensure that users always have up-to-date content. One way to do this is to use a versioning system for our app shell files.

First, we’ll update our service worker to use a cache name that includes the version number:

const cacheName = 'my-cache-v1';

self.addEventListener('install', event => {
event.waitUntil(
caches.open(cacheName).then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js',
'/images/logo.png'
]);
})
);
});

self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys => {
return Promise.all(keys
.filter(key => key !== cacheName)
.map(key => caches.delete(key))
);
})
);
});

This code adds a version number to the cache name and deletes any old caches when the service worker is activated.

Now, whenever we update our app shell files, we can simply update the version number in our service worker and the old cache will be automatically deleted.

Conclusion: In this article, we walked through the steps of building an offline PWA using service workers and caching. We saw how to use the Cache API to store resources locally on the user’s device, and how to use service workers to intercept network requests and provide cached content. We also learned how to handle cache invalidation and updates using versioning.

With these techniques, you can build PWAs that provide a seamless experience for users, even when they are offline.

Happy coding!

--

--

Sai Kumar Gandhi
Sai Kumar Gandhi

Written by Sai Kumar Gandhi

Software engineer and team lead with a passion for building secure and reliable software. Experienced in the cybersecurity domain.

No responses yet