PWA, Workbox, CSP, and Caching
In the world of Web development the words “Best Practice” are often thrown about as if implementation was a trivial detail and giving the impression that everyone else must be doing all this already.
Then you implement these and find that it isn’t so trivial - especially when you try and combine each practice.
I wanted to experiment with making this site into a Progressive Web App (PWA) while maintaining good security through Content Security Policy (CSP) headers and long cache times for static assets.
A PWA requires a manifest file and a service worker. This allows the website to be installed with an icon from the manifest - the app then launches a browser instance on the defined start url, the service worker can cache pages and assets so that the site works offline. A richer app might then sync data back to your server when the device is online later.
I think PWAs have a real potential to efficiently replace mobile apps at least for some use cases.
An efficient cache policy means letting the browser cache local copies of things like CSS files for a long time - ideally a build process creates a CSS file with a unique name which changes whenever the content changes.
This way you can tell the browser to cache the CSS for a long time - but the user always gets updates immediately because any updated file will have a different name.
Combining these two practices was my first challenge - I wanted to tell my service worker to pre-cache a CSS file - but the filename is generated when I build the site.
This meant I couldn’t use a simple hand build service worker with hard coded filenames.
A bit of research led me to believe that Workbox is a common solution so I tried that.
I looked into using Webpack to generate my CSS and Service worker - but I’m no Webpack expert and it seemed to need several plugins. It would need to generate a manifest with all the filenames so that Hugo could generate HTML linking to the generated files using its “data” facility. I didn’t get this to work.
What I did was to use the workbox-cli to generate my service worker on each build - which references the assets Hugo has generated.
Then I had a problem that the script to register my service worker was failing my CSP rules.
It took me a while to figure out what was going on but it seems the service worker generated by Workbox generates inline JavaScript in the page - even though the original JavaScript is loaded from a file. It simply wouldn’t run without script-src 'unsafe-inline';
This is not ideal but after going around in circles a bit I decided to live with it - after all this is a static site with no user generated content so any XSS attack would be very hard to perpetrate.
The result
This site should now be installable as a PWA, it will pre-cache pages from the top menu, and then cache other pages as you travel the site. These pages will then be available offline.
You probably don’t need an offline copy of this site - but it might be a useful demo for me sometime for a customer who does need offline ability.
Remaining issues
There are only two hard things in Computer Science: cache invalidation and naming things.
(The other common error being off-by-one mistakes)
I’m fond of this joke - but it’s true - cache invalidation is very easy to get wrong. You can end up stuck with outdated pages in the cache and no way to clear it.
I’ll have to keep an eye on this one and keep checking my cache policies - also this is why it is good to experiment on a low consequence site first.
If I get time to learn my way around Webpack better I might revisit that route - and possibly customise the service worker a little more.