DEV Community

Cover image for How I built my first Progressive Web App (PWA)
Silvestar Bistrović
Silvestar Bistrović

Posted on • Updated on • Originally published at silvestar.codes

How I built my first Progressive Web App (PWA)

This post was originally published on silvestar.codes.

As of today, my site is available as a Progressive Web App. Yaay! 💯

Lighthouse showing fireworks for perfect scores.

In this post, you would learn more about how and why I added this exciting feature to my site.

Why did I do it

I like to think of myself as a very pedantic person, and I was trying to make my site better from the very first day. At the time when I was building my site, I didn’t understand all metrics from web performance tools like PageSpeed Insights or WebPageTest. But, as I was trying to make my site better, I was learning new techniques, and my website got better and better.

Now my site gets top scores, but one thing was bothering me for some time. It was the Progressive Web App score.

Lighthouse score for Progressive Web App before optimisation.

By looking at the PWA report, I realised my site is ready for PWA. There were only a few issues to resolve. I didn’t understand these issues, but that never stopped me before.

How I did it

As my starting point, I decided to follow the “Your First Progressive Web App” tutorial. The very first step was to update my webmanifest.json file. I added start_url and display options and some required meta tags, like <meta name="apple-mobile-web-app-capable" content="yes">.

{
  "name": "SB site - Silvestar Bistrović website",
  "short_name": "SB site - Silvestar Bistrović website",
  "icons": [
    
  ],
  "theme_color": "#12e09f",
  "background_color": "#fff",
  "start_url": "/offline.html",
  "display": "standalone"
}
Enter fullscreen mode Exit fullscreen mode

Next, I created sw.js file for Service Worker. To register service worker, there is a small snippet that needs to be added to your index page:

// CODELAB: Register service worker.
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
      .then((reg) => {
        console.log('Service worker registered.', reg);
      });
  });
}
Enter fullscreen mode Exit fullscreen mode

The content of the Service Worker file could vary, depending what you want to use achieve with your site. Since my site is quite straightforward, I decided to make use of basic offline experience only. That means I needed an offline.html file for offline experience. It would be a stripped version of my homepage. So I removed external images, and created a placeholder using CSS. I removed external CSS file and inlined it in head section. The only thing left to do was to add favicon files. I am not yet sure if this is need, but I decided to put it there, just in case. Those files aren’t big anyway.

The sw.js file could be broken into four segments:

  • defining constants,
  • installation,
  • activation, and
  • fetching.

First, I defined the cache name and which files to cache.

// constants
const CACHE_NAME = 'sb-cache-v1.3'
const FILES_TO_CACHE = [
  '/offline.html',
  '/favicon/apple-touch-icon.png',
  '/favicon/favicon-32x32.png',
  '/favicon/favicon-16x16.png',
  '/favicon/site.webmanifest',
  '/favicon/safari-pinned-tab.svg',
  '/favicon/favicon.ico',
  '/favicon/mstile-144x144.png',
  '/favicon/browserconfig.xml'
]
Enter fullscreen mode Exit fullscreen mode

Next, I created the install event which opens cache with given cache name and caches the files.

self.addEventListener('install', (event) => {
  // CODELAB: Precache static resources here.
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      console.log('[ServiceWorker] Pre-caching offline page')
      return cache.addAll(FILES_TO_CACHE)
    })
  )
})
Enter fullscreen mode Exit fullscreen mode

After that, I created the activate event, which cleans cached files from disk.

self.addEventListener('activate', (event) => {
  // CODELAB: Remove previous cached data from disk.
  event.waitUntil(
    caches.keys().then(keyList => Promise.all(keyList.map((key) => {
      if (key !== CACHE_NAME) {
        console.log('[ServiceWorker] Removing old cache', key)
        return caches.delete(key)
      }
    })))
  )
})
Enter fullscreen mode Exit fullscreen mode

Finally, I created the fetch event, which handles page navigations only when request .mode is navigate. If the request fails to fetch the item from the network, it tries to fetch the offline.html file.

self.addEventListener('fetch', (event) => {
  // CODELAB: Add fetch event handler here.
  if (event.request.mode === 'navigate') {
    event.respondWith(
      fetch(event.request)
        .catch(() => caches.open(CACHE_NAME)
          .then(cache => cache.match('offline.html')))
    )
  }
})
Enter fullscreen mode Exit fullscreen mode

Final Results

After the deployment, I run the audit for the site and now I have it looks like this:

Lighthouse score for Progressive Web App after optimisation.

I think fireworks deserve another appearance. 💯

Lighthouse showing fireworks for perfect scores.

Next steps

The next steps are to learn more about Workbox. And after that, I plan to add full offline experience for my side project Code Line Daily.

Edit: Continue reading my second article about “How I built my second Progressive Web App (PWA)”.

Top comments (8)

Collapse
 
rajatkantinandi profile image
Rajat Kanti Nandi

Nice article...
When I tried out PWA for the first time it felt like a lot of configuration to do but workbox makes this task very easy.
I used it with create-react-app which has a default setup for workbox but in order to customize the workbox config I used react-app-rewired which lets you override webpack config with your custom configuration & attach your custom service-worker file.
Here I used StaleWhileRevalidate strategy.
This was a simple weather app where I was caching the bundle files, app background images, favicon icon. And I was storing location data, weather API response data in localStorage for offline access if the network fetch call fails...
Finally, I was able to achieve those scores in my app as well...

Source code: github.com/rajatkantinandi/weather...
Live link: WeatHere.now.sh

Collapse
 
starbist profile image
Silvestar Bistrović

Very nice. Thanks for sharing!

Collapse
 
codeuiandy profile image
codeuiandy

Can you make a blog post on how to do it on react.js

Collapse
 
starbist profile image
Silvestar Bistrović

You mean, how to make React.js app as Progressive Web App?

The answer is no, I have never learned React.js.

Collapse
 
codeuiandy profile image
codeuiandy

okay

Thread Thread
 
starbist profile image
Silvestar Bistrović • Edited

See this comment from Rajat: dev.to/rajatkantinandi/comment/i9l1.

Collapse
 
binodnagarkoti profile image
Binod Nagarkoti

Can you please?

Thread Thread
 
starbist profile image
Silvestar Bistrović

See this comment from Rajat: dev.to/rajatkantinandi/comment/i9l1.