| name | progressive-web-app |
| description | "Use when the user wants their web app to work offline or on unreliable networks." |
Progressive Web Apps (PWAs)
Overview
A Progressive Web App is a web application that uses modern browser capabilities to deliver a fast, reliable, and installable experience — even on unreliable networks. The three required pillars are:
- HTTPS — Required in production for service workers to register (localhost is exempt for development).
- Web App Manifest (
manifest.json) — Makes the app installable and defines its appearance on device home screens.
- Service Worker (
sw.js) — A background script that intercepts network requests, manages caches, and enables offline functionality.
When to Use This Skill
- Use when the user wants their web app to work offline or on unreliable networks.
- Use when building a mobile-first web project where users should be able to install the app to their home screen.
- Use when the user asks about caching strategies, service workers, or improving web app performance and resilience.
- Use when the user mentions Workbox, web app manifests, background sync, or push notifications for the web.
- Use when the user asks "can my website be installed like an app?" or "how do I make my site work offline?" — even if they don't use the word PWA.
Deliverables Checklist
Every PWA implementation must include these files at minimum:
Step 1: Web App Manifest (manifest.json)
Defines how the app appears when installed. Must be linked from <head> via <link rel="manifest">.
{
"name": "My Awesome PWA",
"short_name": "MyPWA",
"description": "A fast, offline-capable Progressive Web App.",
"start_url": "/",
"scope": "/",
"display": "standalone",
"orientation": "portrait-primary",
"background_color": "#ffffff",
"theme_color": "#0055ff",
"icons": [
{
"src": "/assets/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
}
Key fields:
display: standalone hides browser UI; minimal-ui shows minimal controls; browser is standard tab.
purpose: "maskable" on icons enables adaptive icons on Android (safe zone matters — keep content in center 80%).
screenshots is optional but required for Chrome's enhanced install dialog on desktop.
Step 2: HTML Shell (index.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Awesome PWA</title>
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#0055ff">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="MyPWA">
<link rel="apple-touch-icon" =>
My PWA
Loading...
Install App
Step 3: Service Worker Registration & Install Prompt (app.js)
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('[App] SW registered, scope:', registration.scope);
} catch (err) {
console.error('[App] SW registration failed:', err);
}
});
}
let deferredPrompt;
const installBtn = document.getElementById('install-btn');
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
if (installBtn) installBtn.hidden = false;
});
if (installBtn) {
installBtn.addEventListener('click', () => {
(!deferredPrompt) ;
deferredPrompt.();
{ outcome } = deferredPrompt.;
.(, outcome);
deferredPrompt = ;
installBtn. = ;
});
}
.(, {
.();
installBtn. = ;
});
Step 4: Service Worker (sw.js)
Cache Versioning (critical — always increment on deploy)
const CACHE_VERSION = 'v1';
const STATIC_CACHE = `static-${CACHE_VERSION}`;
const DYNAMIC_CACHE = `dynamic-${CACHE_VERSION}`;
const APP_SHELL = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/assets/icons/icon-192x192.png',
'/offline.html',
];
Install — Pre-cache the App Shell
self.addEventListener('install', (event) => {
console.log('[SW] Installing...');
event.waitUntil(
caches.open(STATIC_CACHE).then((cache) => {
console.log('[SW] Pre-caching app shell');
return cache.addAll(APP_SHELL);
})
);
self.skipWaiting();
});
Activate — Clean Up Old Caches
self.addEventListener('activate', (event) => {
console.log('[SW] Activating...');
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== STATIC_CACHE && name !== DYNAMIC_CACHE)
.map((name) => {
console.log('[SW] Deleting old cache:', name);
return caches.delete(name);
})
);
})
);
self.clients.claim();
});
Fetch — Caching Strategies
Choose the right strategy per resource type:
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
if (request.method !== 'GET' || url.origin !== location.origin) return;
if (url.pathname.match(/\.(css|js|png|jpg|svg|woff2)$/)) {
event.respondWith(cacheFirst(request));
return;
}
if (request.headers.get('Accept')?.includes('text/html')) {
event.respondWith(networkFirst(request));
return;
}
if (url.pathname.startsWith('/api/')) {
event.respondWith(staleWhileRevalidate(request));
return;
}
});
async function () {
cached = caches.(request);
(cached) cached;
{
response = (request);
cache = caches.();
cache.(request, response.());
response;
} {
(, { : });
}
}
() {
{
response = (request);
cache = caches.();
cache.(request, response.());
response;
} {
cached = caches.(request);
cached || caches.();
}
}
() {
cache = caches.();
cached = cache.(request);
fetchPromise = (request).( {
cache.(request, response.());
response;
});
cached || fetchPromise;
}
Edge Cases & Platform Notes
iOS / Safari Quirks
- Safari supports manifests and service workers but does not support
beforeinstallprompt — users must install via the Share → "Add to Home Screen" menu manually.
- Use the
apple-mobile-web-app-* meta tags (shown in index.html above) for proper iOS integration.
- Safari may clear service worker caches after ~7 days of inactivity (Intelligent Tracking Prevention).
HTTPS Requirement
- Service workers only register on
https:// origins. http://localhost is the only exception for development.
- Use a tool like
mkcert or ngrok if you need HTTPS locally with a custom hostname.
Cache-Busting on Deploy
- Always increment
CACHE_VERSION in sw.js when deploying new assets. This ensures activate clears old caches and users get fresh files.
- A common pattern is to inject the version automatically via your build tool (e.g., Vite, Webpack).
Opaque Responses (cross-origin requests)
- Requests to external origins (e.g., CDN fonts, third-party APIs) return "opaque" responses that cannot be inspected. Cache them with caution — a failed opaque response still gets a
200 status.
- Prefer
staleWhileRevalidate for cross-origin resources, or use a library like Workbox which handles this safely.
Workbox (Optional: Production Shortcut)
For production apps, consider Workbox (Google's PWA library) instead of hand-rolling strategies. It handles edge cases, cache expiry, and versioning automatically.
importScripts('https://storage.googleapis.com/workbox-cdn/releases/7.0.0/workbox-sw.js');
const { registerRoute } = workbox.routing;
const { CacheFirst, NetworkFirst, StaleWhileRevalidate } = workbox.strategies;
const { precacheAndRoute } = workbox.precaching;
precacheAndRoute(self.__WB_MANIFEST || []);
registerRoute(({ request }) => request.destination === 'image', new CacheFirst());
registerRoute(({ request }) => request.mode === 'navigate', new NetworkFirst());
registerRoute(({ request }) => request.destination === 'script', new StaleWhileRevalidate());
Checklist Before Shipping
Diff History
- v00.33.0: Ingested from antigravity-awesome-skills community repo
Why This Skill Exists
Use —
What If Fails
- condition: Recurso ou ferramenta necessária indisponível