PWA & Installability
Configure your app as a PWA with install prompts.
Prerequisites
- HTTPS or localhost — Required for SW and PWA
- Service Worker active — Already set up via SW Template → Build Scripts → Client Registration
- Web App Manifest — Describes your app
Setup with CLI
If you're using the CLI, the PWA install handler is generated automatically when features.pwa is true:
npx @swoff/cli generate
# Generates: swoff/pwa-install.js, public/manifest.jsonConfiguring Install Behavior
Control whether the browser's native install prompt shows naturally or is suppressed:
{
"features": {
"pwa": {
"enabled": true,
"preventDefaultInstall": false
}
}
}preventDefaultInstall | Behavior |
|---|---|
false (default) | Browser shows native prompt naturally. Event is captured for manual triggering. |
true | Browser prompt is suppressed. Dev must call promptInstall() manually. |
Manual Setup
Create Manifest
Create public/manifest.json:
{
"name": "My Offline App",
"short_name": "MyApp",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" }
]
}Link in HTML
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#000000" />Handle Install Prompt
window.addEventListener("beforeinstallprompt", (e) => {
e.preventDefault();
window.deferredInstallPrompt = e as any;
});
async function promptInstall() {
await window.deferredInstallPrompt?.prompt();
const { outcome } = await window.deferredInstallPrompt.userChoice;
}iOS
iOS Safari does not fire the beforeinstallprompt event. Detect and show manual install instructions:
function detectPWAInstallability() {
const isIOS = /iphone|ipad|ipod/i.test(navigator.userAgent);
if (isIOS) {
showIOSInstallInstructions();
return;
}
window.addEventListener("beforeinstallprompt", (e) => {
e.preventDefault();
window.deferredInstallPrompt = e;
});
}
function showIOSInstallInstructions() {
// Guide users to: Share → Add to Home Screen
console.log("iOS: Use Share menu → Add to Home Screen");
}Using the Generated Handler
If you generated swoff/pwa-install.js via the CLI:
import { isInstallable, promptInstall } from './swoff/pwa-install.js';
// Listen for installable event
window.addEventListener('pwa-installable', (e) => {
console.log('PWA can be installed:', e.detail.isInstallable);
// Show your custom install button
});
// When user clicks install button
async function onInstallClick() {
const result = await promptInstall();
console.log('User choice:', result);
}Detect PWA Mode
function isRunningAsPWA(): boolean {
return (
window.matchMedia("(display-mode: standalone)").matches ||
(navigator as any).standalone === true
);
}Generate Icons
Create icons at 192x192 and 512x512 minimum.
Test PWA
- DevTools → Application → Manifest
- Verify manifest loads
- Check install icon in address bar
- Test offline mode
Checklist
- Manifest created and linked
- Icons generated (192x192, 512x512)
- SW registered (see Client Registration)
- Offline works
- Install prompt works
Next Steps
- API Integration — coordinate client fetches with the SW
- IndexedDB Patterns — add data storage
Swoff