This one gives Claude the patterns you need for building offline-first web apps, from service worker registration to caching strategies. You get concrete implementations for cache-first, network-first, and stale-while-revalidate approaches, plus working examples of background sync and push notifications. It covers the web app manifest setup, offline detection handlers, and the app shell architecture. The caching strategies section is especially useful since it shows you exactly when to use each pattern. If you're building something that needs to work when the network drops or you want that installable app experience, this gives you the core patterns without having to piece together MDN docs.
npx -y skills add mindrally/skills --skill pwa-development --agent claude-codeInstalls into .claude/skills of the current project.
You are an expert in building Progressive Web Applications with offline-first capabilities.
{
"name": "My Progressive Web App",
"short_name": "MyPWA",
"description": "A description of your app",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
// Register service worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('SW registered:', registration.scope);
} catch (error) {
console.error('SW registration failed:', error);
}
});
}
// sw.js
const CACHE_NAME = 'v1';
const STATIC_ASSETS = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/offline.html'
];
// Install event - cache static assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(STATIC_ASSETS);
})
);
self.skipWaiting();
});
// Activate event - cleanup old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
})
);
self.clients.claim();
});
// Fetch event - serve from cache or network
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
}).catch(() => {
return caches.match('/offline.html');
})
);
});
async function cacheFirst(request) {
const cached = await caches.match(request);
return cached || fetch(request);
}
async function networkFirst(request) {
try {
const response = await fetch(request);
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
return response;
} catch {
return caches.match(request);
}
}
async function staleWhileRevalidate(request) {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(request);
const fetchPromise = fetch(request).then((response) => {
cache.put(request, response.clone());
return response;
});
return cached || fetchPromise;
}
// Register sync in main app
async function registerSync() {
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('sync-data');
}
// Handle sync in service worker
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-data') {
event.waitUntil(syncData());
}
});
async function syncData() {
const data = await getQueuedData();
await fetch('/api/sync', {
method: 'POST',
body: JSON.stringify(data)
});
}
// Request permission
async function requestNotificationPermission() {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: PUBLIC_VAPID_KEY
});
// Send subscription to server
}
}
// Handle push in service worker
self.addEventListener('push', (event) => {
const data = event.data.json();
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icons/icon-192.png'
})
);
});
// Check online status
window.addEventListener('online', () => {
console.log('Back online');
syncPendingData();
});
window.addEventListener('offline', () => {
console.log('Offline mode');
showOfflineIndicator();
});
mindrally/skills
giuseppe-trisciuoglio/developer-kit
syncfusion/react-ui-components-skills
supercent-io/skills-template
binjuhor/shadcn-lar