main

My blog website needs to be a progressive web application. Or in other words, a mobile app that looks like it knows what it’s doing. Lighthouse also needs to give me a good score.

So this was an interesting problem I never faced before! Make a statically generated website a progressive web application (PWA). The placement of PWA requisite files for a normal web application is familiar, but making a hugo app changes it up a bit. So I read some articles!

I learned that the manifest.json and sw.js files should go with the ./static folder. The icons can also be accessed through the ./static/icons folder, or where ever I placed them.

But how am I to make the website know where these files are?

I just pulled out some of the HTML template files from the theme, and I put them into my own ./layouts folder. In the head.html and footer.html templates, I put the corresponding script, meta, and link tags.

What about a caching strategy? Since website has many pages and it is mostly static, I should be fine caching first then defaulting to the network request. The posts and home page should be cached but not the posts page. The posts page should never cache, because that is where I need new posts to show up. Caching also should not occur when I am developing or in the drafts release, just to make things easier to change. So my service worker file eventually came to look like the following code snippet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const VERSION = 2;
const CACHE = 'gremlich-me-blog-v' + VERSION;

self.addEventListener('install', evt =>
    evt.waitUntil(
        caches
            .open(CACHE)
            .then(cache => cache.addAll(['/', './index.html', '/js/instant.js', '/css/basic.css', '/img/search.png']))
    )
);

self.addEventListener('fetch', event =>
    event.respondWith(
        caches.match(event.request).then(
            response =>
                response ||
                fetch(event.request)
                    .then(response => {
                        const responseBody = response.clone();
                        const cacheWhiteList = ['style', 'css', 'js', `posts/${new Date(Date.now()).getFullYear()}`];
                        const cacheBlackList = ['json', 'livereload', 'localhost', 'drafts'];

                        if (
                            cacheWhiteList.some(filetype =>
                                cacheBlackList.some(ft => responseBody.url.includes(ft))
                                    ? false
                                    : responseBody.url.includes(filetype)
                            )
                        )
                            caches.open(CACHE).then(cache => cache.put(event.request, responseBody));

                        return response;
                    })
                    .catch(() => caches.match(event.request))
        )
    )
);

self.addEventListener('activate', evt =>
    evt.waitUntil(caches.keys().then(keyList => Promise.all(keyList.map(key => key !== CACHE && caches.delete(key)))))
);

self.addEventListener('message', event => event.data.action === 'skipWaiting' && self.skipWaiting());

Thanks to this service worker file, there are a set of default files that are cached in the 'install' event. Those files are probably critical. Then in the 'fetch' event, the intent is to cache any file related to a post that is not being developed in localhost or in drafts release.