A Progressive Web App is a website that behaves like a native app. Users install it on the home screen, run it offline, receive push notifications, and use it full-screen without browser chrome. All without an app store submission.
The tech has matured. In 2026 PWAs run on Chrome, Edge, Safari, Firefox, and Samsung Internet. Apple has expanded iOS PWA capabilities (though still narrower than Android). The gap with native shrinks every year.
Turning an existing site into a PWA is not a rewrite. It is four additions: a manifest file, a service worker, an icon set, and a few configuration lines. This checklist walks through them.
Web App Manifest
A JSON file that tells the browser how the app behaves when installed:
`json
{
"name": "Your App Name",
"short_name": "App",
"description": "A brief description of your app",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2563eb",
"orientation": "any",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/icons/icon-512-maskable.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
`
Link from HTML:
`html
`
Fields worth tuning:
name: full name shown during install.short_name: home-screen label, keep under 12 characters.start_url: the page that opens on launch.display:standalonefor app-like,fullscreenfor games,minimal-uito keep browser controls.theme_color: colors the address bar and status bar.background_color: splash background while the app boots.
Generate the full icon set with the Favicon Generator. Required minimum: 192×192 and 512×512 PNG plus a 512×512 maskable variant for Android.

Service Worker Essentials
The service worker runs in the background and enables offline, caching, and push:
`javascript
// sw.js
const CACHE_NAME = 'v1';
const ASSETS = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/icons/icon-192.png'
];
self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS)) ); });
self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then(cached => cached || fetch(event.request)) ); });
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys.filter(key => key !== CACHE_NAME).map(key => caches.delete(key))
)
)
);
});
`
Register from your main script:
`javascript
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
`
Three caching strategies cover most cases:
- Cache first (above): serve from cache, fall back to network. Best for static assets.
- Network first: try network, fall back to cache. Best for dynamic content.
- Stale while revalidate: serve from cache immediately, refresh the cache in the background. Best balance of speed and freshness.
For production, use Workbox (from Google). It handles caching, expiration, and precaching with much less custom code.
Minify HTML before precaching with the HTML Minifier so the precache footprint is as small as possible.
The service worker runs in the background and enables offline, caching, and push: ```javascript // sw.js const CACHE_NAME = 'v1'; const ASSETS = [ '/', '/index.html', '/styles.css', '/app.js', '/icons/icon-192.png' ]; self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS)) ); }); self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then(cached => cached || fetch(event.request)) ); }); self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then(keys => Promise.all( keys.filter(key => key !== CACHE_NAME).map(key => caches.delete(key)) ) ) ); }); ``` Register from your main script: ```javascript if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js'); } ``` Three caching strategies cover most cases: - **Cache first** (above): serve from cache, fall back to network.
Designing the Offline Experience
A service worker is the engine. The offline UX is design work.
- Offline fallback page: a dedicated page that explains the connection is down and lists what is still available. Precache it on install.
- Cached content indicator: when serving stale content, say so. A subtle banner like "Viewing cached version. Connect to refresh." sets expectations.
- Forms while offline: store submissions in IndexedDB and sync when the connection returns via the Background Sync API.
- Graceful degradation: features that need the network (search, profiles, live data) should render meaningful fallback states, not blank screens or generic errors.
- Test offline: in Chrome DevTools, Application > Service Workers > Offline. Walk every critical path: navigation, form submit, content viewing.
Before precaching images, run them through the Image Compressor. A smaller cache footprint means faster installs and less device storage used.
Install, Notifications, and Updates
Install prompt: Chrome fires beforeinstallprompt when your PWA meets the criteria (manifest, service worker, HTTPS). Defer it and trigger from your own button:
`javascript
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => { e.preventDefault(); deferredPrompt = e; showInstallButton(); });
function installApp() {
deferredPrompt.prompt();
deferredPrompt.userChoice.then(result => {
if (result.outcome === 'accepted') console.log('App installed');
deferredPrompt = null;
});
}
`
Timing: never on first visit. Wait until the user has visited 3+ pages, spent 2+ minutes, or completed a meaningful action. First-visit prompts convert at single digits.
iOS: Safari does not fire beforeinstallprompt. Users install via Share > Add to Home Screen. Detect iOS Safari and show custom instructions with an arrow.
Push notifications: ask after the user has gotten value, not before. Explain what the notifications will contain. First-visit permission requests run around 3% acceptance. After engagement, 15-25% is realistic.
App updates: a new service worker detects automatically. Show a "New version available" banner with a refresh button instead of force-refreshing mid-task.

The Launch Checklist
Before you ship:
Required
- [ ] HTTPS (PWAs require secure context).
- [ ] Manifest with name, icons, start_url, display.
- [ ] Icons: 192×192, 512×512, plus maskable 512×512 for Android.
- [ ] Service worker registered and caching critical assets.
- [ ] Offline fallback page works disconnected.
- [ ] Viewport meta tag:
. - [ ]
theme_colorin both manifest and meta tag.
Recommended
- [ ] Cache-first for static assets, network-first for API calls.
- [ ] Custom install prompt triggered after engagement.
- [ ] Splash screen (background_color + icon in manifest).
- [ ] Responsive design across phone, tablet, desktop.
- [ ] Lighthouse PWA audit green.
- [ ] First Contentful Paint under 2 seconds.
Advanced
- [ ] Push notifications with permission request after engagement.
- [ ] Background sync for offline form submissions.
- [ ] Periodic background sync for content updates.
- [ ] Share Target API for receiving shared content.
- [ ] Manifest shortcuts for quick actions.
- [ ] Manifest screenshots for richer install UI.
Run Lighthouse (Chrome DevTools > Lighthouse > Progressive Web App) to verify everything before launch.
FAQ
Do PWAs work on iOS?
Yes, with limits. Safari supports service workers, the web app manifest, and home screen install. Push notifications arrived in iOS 16.4 (2023), background sync is limited, and some Android-available Web APIs are still missing. Test on iOS, the behavior diverges from Chrome on Android.
Do I need to submit a PWA to an app store?
No. PWAs install from the browser. You can optionally submit to Google Play (via Trusted Web Activities), the Microsoft Store, and the Samsung Galaxy Store for extra distribution. Apple's App Store does not accept PWAs.
How big should the cache be?
Keep the initial precache (assets for offline) under 5 MB. Total cache can grow with use; add expiration to keep it bounded. Most browsers allow 50-100 MB per origin.
Can a PWA access device features?
Yes. MediaDevices (camera), Geolocation, accelerometer, gyroscope, and Web Bluetooth all work. File System Access, WebUSB, and Web Serial are Chromium-only. Each API needs explicit user permission. Check caniuse.com for browser-specific support.
### Do PWAs work on iOS.
Markdown Table Generator: Build Clean Tables Without the Pain
Markdown tables are simple until the pipes and dashes stop lining up. Learn the syntax, alignment tricks, and a free tool that formats tables for you.
CSV to JSON: Convert Spreadsheet Data for APIs and Code
Turn a CSV export into clean JSON for APIs, imports, and scripts. Learn how the conversion works, common pitfalls with types and quotes, and a free tool.
JSON Guide: Format, Validate, and Convert JSON Files
JSON guide for developers: syntax rules, common parse errors, formatting and schema validation, plus how to convert between JSON and CSV files.
