Cover image for Painless PWA

Painless PWA

yne profile image Rémy F. ・2 min read

Disclaimer: PWA is still evolving, this article may soon become obsolete

An install-ready PWA is made of :

  • an HTML page, with some specific <meta> and <link> tags
  • a Service Worker script (sw.js), that will cache your assets for offline use.
  • some icons for desktop and splash screen
  • a manifest.json, that will describe your PWA to the installer.

Minimal manifest.json example

  "name": "Name",
  "short_name": "Short",
  "theme_color": "#1d3557",
  "background_color": "#ededed",
  "display": "standalone",
  "start_url": "./",
  "icons": [
      "src": "192x192.png",
      "type": "image/png",
      "sizes": "192x192"
      "src": "512x512.png",
      "type": "image/png",
      "sizes": "512x512"

The main downside of the manifest.json is that it simply repeat what you've already set in your index.html (icons, name, theme_color ...).

A simpler solution is to automatically generate it from those tags while registering your serviceWorker :


<!doctype html>
<html lang=en>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="theme-color" content="#317EFB"/>
<meta name="short-name" content="short"/>
<meta name="background-color" content="#FFFFFF"/>
<meta name="display" content="standalone"/>
<meta name="description" content="description"/>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<link rel="apple-touch-icon" href="192x192.png">
<link rel="splash-icon" href="512x512.png">
<link rel="worker" href="sw.js">
<script type=module defer>
const qsa=s=>[...document.querySelectorAll(s)];
const i=(s,x)=>({src:qsa(`[rel=${s}-icon]`)[0].href,type:"image/png",sizes:`${x}x${x}`});
//TODO: submit this code to iojscc

<h1>I'm PWA</h1>


const cacheList = ["./"]; // add more if needed
const cacheName = "app-42";
self.addEventListener('install', e => e.waitUntil(caches.open(cacheName).then(cache => cache.addAll(cacheList).then(() => self.skipWaiting()))));
self.addEventListener('activate', e => e.waitUntil(self.clients.claim()));
self.addEventListener('fetch', e => e.respondWith(caches.open(cacheName).then(cache => cache.match(e.request, {ignoreSearch: true})).then(response => (e.request.cache !== 'only-if-cached' || e.request.mode === 'same-origin') ? (response || fetch(e.request)) : response)));

You shall get a perfect audit score (if your server support HTTP/2)

Alt Text

Posted on by:

yne profile

Rémy F.




markdown guide

Great MVP, I have only turned on pwa via gatsby plugin. Cool to get a taste of how it gets implemented.


Nice ,is there any PWA starter tempale like "htmlBoilerPlate" but with PWA ?


Replace the manifest generator <script> with the example manifest.json you will get the simplest installable PWA that pass the audit criterias.
I tested on desktop chrome, but not on others platforms/browsers.


What is the difference (pros / cons) between link rel="worker" and script src="worker" implementation?


<script src=sw.js></script> will not register the script as a Service Worker (running in a background thread).
You need to navigator.serviceWorker.register('/path/to/your/worker.js') for that.
I used <link rel="worker" href="sw.js"> because it allows me to give a relative URL, but get an absolute URL when accessing the href attribute.