Pwa Development
Progressive Web Apps - service workers, caching strategies, offline, Workbox
Specification
- Skill ID
- alinaqi/maggy/pwa-development
- Publisher
- alinaqi
- Repository
- maggy
- Installs
- 0
- Files
- 1
- Synced
- Sep 23, 2026
How to use it
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
alinaqi/maggy/pwa-developmentInstalls these files- SKILL.md
What this skill tells the agent
PWA Development Skill
Purpose: Build Progressive Web Apps that work offline, install like native apps, and deliver fast, reliable experiences across all devices.
Core PWA Requirements
┌─────────────────────────────────────────────────────────────────┐
│ THE THREE PILLARS OF PWA │
│ ───────────────────────────────────────────────────────────── │
│ │
│ 1. HTTPS │
│ Required for service workers and security. │
│ localhost allowed for development. │
│ │
│ 2. SERVICE WORKER │
│ JavaScript that runs in background. │
│ Enables offline, caching, push notifications. │
│ │
│ 3. WEB APP MANIFEST │
│ JSON file describing app metadata. │
│ Enables installation and app-like experience. │
├─────────────────────────────────────────────────────────────────┤
│ INSTALLABILITY CRITERIA (Chrome) │
│ ───────────────────────────────────────────────────────────── │
│ • HTTPS (or localhost) │
│ • Service worker with fetch handler │
│ • Web app manifest with: name, icons (192px + 512px), │
│ start_url, display: standalone/fullscreen/minimal-ui │
└─────────────────────────────────────────────────────────────────┘Web App Manifest
Required Fields
{
"name": "My Progressive Web App",
"short_name": "MyPWA",
"description": "A description of what the app does",
"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"
},
{
"src": "/icons/icon-512-maskable.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}Enhanced Manifest (Full Features)
{
"name": "My Progressive Web App",
"short_name": "MyPWA",
"description": "A full-featured PWA",
"start_url": "/?source=pwa",
"scope": "/",
"display": "standalone",
"orientation": "portrait-primary",
"background_color": "#ffffff",
"theme_color": "#3367D6",
"dir": "ltr",
"lang": "en",
"categories": ["productivity", "utilities"],
"icons": [
{ "src": "/icons/icon-72.png", "sizes": "72x72", "type": "image/png" },
{ "src": "/icons/icon-96.png", "sizes": "96x96", "type": "image/png" },
{ "src": "/icons/icon-128.png", "sizes": "128x128", "type": "image/png" },
{ "src": "/icons/icon-144.png", "sizes": "144x144", "type": "image/png" },
{ "src": "/icons/icon-152.png", "sizes": "152x152", "type": "image/png" },
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/icon-384.png", "sizes": "384x384", "type": "image/png" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/icons/icon-maskable.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
],
"screenshots": [
{
"src": "/screenshots/desktop.png",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide"
},
{
"src": "/screenshots/mobile.png",
"sizes": "750x1334",
"type": "image/png",
"form_factor": "narrow"
}
],
"shortcuts": [
{
"name": "New Item",
"short_name": "New",
"description": "Create a new item",
"url": "/new?source=shortcut",
"icons": [{ "src": "/icons/shortcut-new.png", "sizes": "192x192" }]
}
],
"share_target": {
"action": "/share",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"title": "title",
"text": "text",
"url": "url",
"files": [{ "name": "files", "accept": ["image/*"] }]
}
},
"protocol_handlers": [
{
"protocol": "web+myapp",
"url": "/handle?url=%s"
}
],
"file_handlers": [
{
"action": "/open-file",
"accept": {
"text/plain": [".txt"]
}
}
]
}Manifest Checklist
- [ ]
nameandshort_namedefined - [ ]
start_urlset (use query param for analytics) - [ ]
displayset tostandaloneorfullscreen - [ ] Icons: 192x192 and 512x512 minimum
- [ ] Maskable icon included for Android adaptive icons
- [ ]
theme_colormatches app design - [ ]
background_colorfor splash screen - [ ] Screenshots for richer install UI (optional)
- [ ] Shortcuts for quick actions (optional)
Service Worker Patterns
Basic Service Worker
// sw.js
const CACHE_NAME = 'app-cache-v1';
const STATIC_ASSETS = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/app.js',
'/offline.html'
];
// Install: Cache static assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(STATIC_ASSETS))
.then(() => self.skipWaiting())
);
});
// Activate: Clean old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys()
.then((keys) => Promise.all(
keys
.filter((key) => key !== CACHE_NAME)
.map((key) => caches.delete(key))
))
.then(() => self.clients.claim())
);
});
// Fetch: Serve from cache, fall back to network
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((cached) => cached || fetch(event.request))
.catch(() => caches.match('/offline.html'))
);
});Registration
// main.js
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/'
});
console.log('SW registered:', registration.scope);
} catch (error) {
console.error('SW registration failed:', error);
}
});
}Caching Strategies
Strategy Selection Guide
| Strategy | Use Case | Description |
|---|---|---|
| Cache First | Static assets (CSS, JS, images) | Check cache, fall back to network |
| Network First | API responses, dynamic content | Try network, fall back to cache |
| Stale While Revalidate | Semi-static content (avatars, articles) | Serve cache immediately, update in background |
| Network Only | Non-cacheable requests (analytics) | Always use network |
| Cache Only | Offline-only assets | Only serve from cache |
Cache First (Offline First)
// Best for: Static assets that rarely change
self.addEventListener('fetch', (event) => {
if (event.request.destination === 'image' ||
event.request.destination === 'style' ||
event.request.destination === 'script') {
event.respondWith(
caches.match(event.request)
.then((cached) => {
if (cached) return cached;
return fetch(event.request).then((response) => {
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, clone);
});
return response;
});
})
);
}
});Network First (Fresh First)
// Best for: API data, frequently updated content
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/api/')) {
event.respondWith(
fetch(event.request)More from alinaqi
Use this skill in a project
Open any RiverX project, search the Skills panel for alinaqi/maggy/pwa-development, and install it in one click.
