Skip to content

Generate PWAs #7

@slevin48

Description

@slevin48

here’s the minimal, no-framework recipe to turn a single index.html into a PWA.

  1. Add a web app manifest

Create manifest.webmanifest at your site root:

{
"name": "My App",
"short_name": "MyApp",
"start_url": "/?source=pwa",
"scope": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0f172a",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
]
}

  1. Reference it and register a Service Worker in index.html

In your :

Before (or in a module script), register the SW:

<script> if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js'); }); } </script>
  1. Add a basic Service Worker

Create sw.js at your site root. This precaches your app shell and adds simple runtime caching.

/* sw.js */
const APP_VERSION = 'v1';
const APP_CACHE = app-cache-${APP_VERSION};
const RUNTIME_CACHE = 'runtime';

const PRECACHE_ASSETS = [
'/', // if your server serves index.html at /
'/index.html',
'/styles.css', // optional
'/app.js', // optional
'/manifest.webmanifest',
'/icons/icon-192.png',
'/icons/icon-512.png'
];

self.addEventListener('install', event => {
event.waitUntil(
caches.open(APP_CACHE).then(cache => cache.addAll(PRECACHE_ASSETS))
);
self.skipWaiting();
});

self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.map(k => (k.startsWith('app-cache-') && k !== APP_CACHE) ? caches.delete(k) : null))
)
);
self.clients.claim();
});

// Cache-first for same-origin static, network-first for HTML, fallback to cache offline
self.addEventListener('fetch', event => {
const req = event.request;
const url = new URL(req.url);

// HTML pages: network-first so updates show up
if (req.mode === 'navigate' || (req.destination === 'document')) {
event.respondWith(
fetch(req).then(res => {
const copy = res.clone();
caches.open(RUNTIME_CACHE).then(c => c.put(req, copy));
return res;
}).catch(() => caches.match(req).then(r => r || caches.match('/index.html')))
);
return;
}

// Same-origin static: cache-first
if (url.origin === location.origin) {
event.respondWith(
caches.match(req).then(cached => cached || fetch(req).then(res => {
const copy = res.clone();
caches.open(RUNTIME_CACHE).then(c => c.put(req, copy));
return res;
}))
);
return;
}

// Third-party (e.g., images, fonts): stale-while-revalidate
event.respondWith(
caches.match(req).then(cached => {
const network = fetch(req).then(res => {
caches.open(RUNTIME_CACHE).then(c => c.put(req, res.clone()));
return res;
}).catch(() => cached);
return cached || network;
})
);
});

  1. File structure

/ (https, not http)
├── index.html
├── manifest.webmanifest
├── sw.js
└── icons/
├── icon-192.png (maskable if possible)
└── icon-512.png

  1. Serve over HTTPS

PWAs require secure context. Easiest options:

GitHub Pages, Netlify, Vercel, Cloudflare Pages — all serve HTTPS by default.

  1. Test it

Open DevTools → Application → Manifest (Chrome) to verify the manifest and icons.

Lighthouse → “Progressive Web App” to audit installability & offline.

Reload; you should see “Install”/“Add to Home Screen” available.

  1. Updates & versioning

Bump APP_VERSION in sw.js when you deploy changes to precached files.

The activate handler above cleans old caches automatically.

Consider adding a small “New version available – Refresh” toast by listening to registration.waiting on the page and calling postMessage({type:'SKIP_WAITING'}) from the SW to swap in-place.

  1. Nice-to-haves (optional)

maskable icons: export icons with safe padding for Android install banners.

Offline fallback page: add /offline.html and return it when fetch fails.

Content-Security-Policy: lock down sources once things work.

Workbox: if the app grows, replace the manual SW with Workbox build plugins for declarative precaching and strategies.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions