
Pwa Patterns
- 24 installs
- 213 repo stars
- Updated August 4, 2026
- yonatangross/orchestkit
Helps with ai & agent building tasks.
About
pwa-patterns is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- pwa-patterns
- AI & Agent Building
- AI-coding skill
Pwa Patterns by the numbers
- 24 all-time installs (skills.sh)
- Ranked #9,876 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/yonatangross/orchestkit --skill pwa-patternsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 24 |
|---|---|
| repo stars | ★ 213 |
| Last updated | August 4, 2026 |
| Repository | yonatangross/orchestkit ↗ |
What it does
Helps with ai & agent building tasks.
Files
PWA Patterns
Progressive Web App patterns using Workbox 7.x for service worker management, offline-first strategies, and app-like experiences.
Service Worker Lifecycle
Installing -> Waiting -> Active
│ │ │
install activated fetch events
(precache) when old SW (runtime cache)
is goneWorkbox: Generate Service Worker
// build-sw.js (Node.js)
const { generateSW } = require('workbox-build');
async function buildServiceWorker() {
await generateSW({
globDirectory: 'dist/',
globPatterns: ['**/*.{html,js,css,png,jpg,json,woff2}'],
swDest: 'dist/sw.js',
clientsClaim: true,
skipWaiting: true,
navigateFallback: '/index.html',
navigateFallbackDenylist: [/^\/api\//],
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.example\.com\//,
handler: 'NetworkFirst',
options: { cacheName: 'api-cache', networkTimeoutSeconds: 10 },
},
{
urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp)$/,
handler: 'CacheFirst',
options: { cacheName: 'images', expiration: { maxEntries: 60, maxAgeSeconds: 30 * 24 * 60 * 60 } },
},
],
});
}Caching Strategies
// CacheFirst: Static assets that rarely change
registerRoute(/\.(?:js|css|woff2)$/, new CacheFirst({
cacheName: 'static-v1',
plugins: [new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 365 * 24 * 60 * 60 })],
}));
// NetworkFirst: API calls (fresh data preferred)
registerRoute(/\/api\//, new NetworkFirst({
cacheName: 'api-cache',
networkTimeoutSeconds: 10,
plugins: [new CacheableResponsePlugin({ statuses: [0, 200] })],
}));
// StaleWhileRevalidate: User avatars, non-critical images
registerRoute(/\/avatars\//, new StaleWhileRevalidate({ cacheName: 'avatars' }));
// NetworkOnly: Auth endpoints
registerRoute(/\/auth\//, new NetworkOnly());VitePWA Integration
// vite.config.ts
import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({
plugins: [
VitePWA({
registerType: 'autoUpdate',
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
runtimeCaching: [{ urlPattern: /^https:\/\/api\./, handler: 'NetworkFirst' }],
},
manifest: {
name: 'My PWA App',
short_name: 'MyPWA',
theme_color: '#4f46e5',
icons: [
{ src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
{ src: '/icon-512.png', sizes: '512x512', type: 'image/png' },
],
},
}),
],
});Web App Manifest
{
"name": "My Progressive Web App",
"short_name": "MyPWA",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#4f46e5",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
]
}React Hooks
Install Prompt Hook
import { useState, useEffect } from 'react';
interface BeforeInstallPromptEvent extends Event {
prompt: () => Promise<void>;
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>;
}
export function useInstallPrompt() {
const [installPrompt, setInstallPrompt] = useState<BeforeInstallPromptEvent | null>(null);
const [isInstalled, setIsInstalled] = useState(false);
useEffect(() => {
const handler = (e: BeforeInstallPromptEvent) => { e.preventDefault(); setInstallPrompt(e); };
window.addEventListener('beforeinstallprompt', handler as EventListener);
if (window.matchMedia('(display-mode: standalone)').matches) setIsInstalled(true);
return () => window.removeEventListener('beforeinstallprompt', handler as EventListener);
}, []);
const promptInstall = async () => {
if (!installPrompt) return false;
await installPrompt.prompt();
const { outcome } = await installPrompt.userChoice;
setInstallPrompt(null);
if (outcome === 'accepted') { setIsInstalled(true); return true; }
return false;
};
return { canInstall: !!installPrompt, isInstalled, promptInstall };
}Offline Status Hook
export function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}Background Sync
// sw.js
import { BackgroundSyncPlugin } from 'workbox-background-sync';
import { registerRoute } from 'workbox-routing';
import { NetworkOnly } from 'workbox-strategies';
registerRoute(
/\/api\/forms/,
new NetworkOnly({ plugins: [new BackgroundSyncPlugin('formQueue', { maxRetentionTime: 24 * 60 })] }),
'POST'
);Anti-Patterns (FORBIDDEN)
// NEVER: Cache everything with no expiration (storage bloat)
// NEVER: Skip clientsClaim (old tabs stay on old SW)
// NEVER: Cache authentication tokens (security risk)
// NEVER: Precache dynamic content (changes frequently)
// NEVER: Forget offline fallback for navigation
// NEVER: Cache POST responsesPWA Checklist
- [ ] Service worker registered
- [ ] Manifest with icons (192px + 512px maskable)
- [ ] HTTPS enabled
- [ ] Offline page works
- [ ] Responsive design
- [ ] Fast First Contentful Paint (< 1.8s)
Key Decisions
| Decision | Recommendation |
|---|---|
| SW generator | generateSW for simple, injectManifest for custom |
| API caching | NetworkFirst for critical data |
| Static assets | CacheFirst with versioned filenames |
| Update strategy | Prompt user for major changes |
Related Skills
caching-strategies- Backend caching patternscore-web-vitals- Performance metricsstreaming-api-patterns- Real-time updates
Workbox Caching Strategies
Strategy Overview
| Strategy | Network | Cache | Best For |
|---|---|---|---|
| CacheFirst | Fallback | Primary | Static assets, fonts |
| NetworkFirst | Primary | Fallback | API data, user content |
| StaleWhileRevalidate | Background | Immediate | Balance of fresh + fast |
| NetworkOnly | Always | Never | Auth, POST requests |
| CacheOnly | Never | Always | Offline-only content |
CacheFirst
import { CacheFirst } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
workbox.routing.registerRoute(
/\.(?:js|css|woff2)$/,
new CacheFirst({
cacheName: 'static-resources',
plugins: [
new ExpirationPlugin({
maxEntries: 100,
maxAgeSeconds: 365 * 24 * 60 * 60, // 1 year
}),
],
})
);Use when:
- Asset has hash in filename (cache-busting)
- Content rarely/never changes
- Fast response critical
NetworkFirst
import { NetworkFirst } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
workbox.routing.registerRoute(
/\/api\//,
new NetworkFirst({
cacheName: 'api-cache',
networkTimeoutSeconds: 10, // Fall back after 10s
plugins: [
new ExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 5 * 60, // 5 minutes
}),
],
})
);Use when:
- Fresh data preferred
- Offline fallback needed
- API responses
StaleWhileRevalidate
import { StaleWhileRevalidate } from 'workbox-strategies';
workbox.routing.registerRoute(
/\/avatars\//,
new StaleWhileRevalidate({
cacheName: 'avatars',
plugins: [
new ExpirationPlugin({
maxEntries: 100,
maxAgeSeconds: 7 * 24 * 60 * 60,
}),
],
})
);Use when:
- Speed important, staleness acceptable
- Content updates periodically
- User avatars, thumbnails
Background Sync
import { BackgroundSyncPlugin } from 'workbox-background-sync';
import { NetworkOnly } from 'workbox-strategies';
const bgSyncPlugin = new BackgroundSyncPlugin('formQueue', {
maxRetentionTime: 24 * 60, // 24 hours
});
workbox.routing.registerRoute(
/\/api\/submit/,
new NetworkOnly({
plugins: [bgSyncPlugin],
}),
'POST'
);Use when:
- Form submissions
- Analytics events
- Offline-capable POSTs
Cache Expiration
import { ExpirationPlugin } from 'workbox-expiration';
new ExpirationPlugin({
maxEntries: 60, // Max items in cache
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
purgeOnQuotaError: true, // Delete on storage quota error
})Cacheable Response
import { CacheableResponsePlugin } from 'workbox-cacheableResponse';
new CacheableResponsePlugin({
statuses: [0, 200], // Cache opaque (0) and OK (200)
headers: {
'X-Is-Cacheable': 'true', // Custom header check
},
})Related skills
AI & Agent Buildingagents