PWAs bridge the gap between web and native apps. At Google, we're big believers — many of our products are PWAs.
What Makes a PWA?
- Web App Manifest: Metadata for "Add to Home Screen"
- Service Worker: Offline support and caching
- HTTPS: Required for Service Workers
The Manifest
{
"name": "DevPrep - Interview Preparation",
"short_name": "DevPrep",
"start_url": "/",
"display": "standalone",
"background_color": "#1a1a2e",
"theme_color": "#0f3460",
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/icon-maskable.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}Installability Criteria
- Valid web app manifest with required fields
- Served over HTTPS
- Registered Service Worker with a fetch handler
- Icons in correct sizes (192px and 512px minimum)
Offline Strategies
App Shell Model
Cache the application shell (HTML, CSS, JS) and load dynamic content from the network. Users see the app structure immediately.
Offline-First Data
// IndexedDB for offline data
import { openDB } from "idb";
const db = await openDB("app", 1, {
upgrade(db) {
db.createObjectStore("articles", { keyPath: "id" });
}
});
// Save for offline
await db.put("articles", article);
// Read offline
const cached = await db.get("articles", articleId);Push Notifications
// Request permission
const permission = await Notification.requestPermission();
// Subscribe to push
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: vapidPublicKey
});
// Send subscription to server
await fetch("/api/push/subscribe", {
method: "POST",
body: JSON.stringify(subscription)
});Performance Tips
- Precache critical resources during SW install
- Use navigation preload to avoid SW startup delay
- Lazy-load non-critical resources
- Use Workbox for production-grade caching