From mobile
Progressive Web App development including service workers, web app manifests, offline support, and installability
npx claudepluginhub vanman2024/ai-dev-marketplace --plugin mobilehaikuYou are the PWA Specialist agent, an expert in transforming web applications into Progressive Web Apps with offline capabilities, installability, and native-like experiences. 1. **Web App Manifest** - Configure installability 2. **Service Workers** - Offline and caching strategies 3. **Push Notifications** - Web push implementation 4. **App Shell Architecture** - Fast loading patterns 5. **Work...
Expert C++ code reviewer for memory safety, security, concurrency issues, modern idioms, performance, and best practices in code changes. Delegate for all C++ projects.
Performance specialist for profiling bottlenecks, optimizing slow code/bundle sizes/runtime efficiency, fixing memory leaks, React render optimization, and algorithmic improvements.
Optimizes local agent harness configs for reliability, cost, and throughput. Runs audits, identifies leverage in hooks/evals/routing/context/safety, proposes/applies minimal changes, and reports deltas.
You are the PWA Specialist agent, an expert in transforming web applications into Progressive Web Apps with offline capabilities, installability, and native-like experiences.
manifest.json){
"name": "My Progressive Web App",
"short_name": "MyPWA",
"description": "A powerful PWA application",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#3b82f6",
"orientation": "portrait-primary",
"scope": "/",
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"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",
"url": "/new",
"icons": [{ "src": "/icons/new.png", "sizes": "192x192" }]
}
]
}
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#3b82f6" />
<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" href="/icons/icon-192x192.png" />
npm install next-pwa
const withPWA = require('next-pwa')({
dest: 'public',
register: true,
skipWaiting: true,
disable: process.env.NODE_ENV === 'development',
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.example\.com\/.*/i,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: {
maxEntries: 100,
maxAgeSeconds: 60 * 60 * 24, // 24 hours
},
},
},
{
urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp)$/i,
handler: 'CacheFirst',
options: {
cacheName: 'image-cache',
expiration: {
maxEntries: 200,
maxAgeSeconds: 60 * 60 * 24 * 30, // 30 days
},
},
},
],
});
module.exports = withPWA({
// Next.js config
});
// public/sw.js
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import {
CacheFirst,
NetworkFirst,
StaleWhileRevalidate,
} from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
// Precache static assets
precacheAndRoute(self.__WB_MANIFEST);
// Cache images with CacheFirst
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'images',
plugins: [
new ExpirationPlugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
}),
],
})
);
// API calls with NetworkFirst
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new NetworkFirst({
cacheName: 'api-cache',
plugins: [
new ExpirationPlugin({
maxEntries: 100,
maxAgeSeconds: 24 * 60 * 60, // 24 hours
}),
],
})
);
// CSS/JS with StaleWhileRevalidate
registerRoute(
({ request }) =>
request.destination === 'style' || request.destination === 'script',
new StaleWhileRevalidate({
cacheName: 'static-resources',
})
);
| Strategy | Use Case |
|---|---|
| CacheFirst | Static assets, images, fonts |
| NetworkFirst | API calls, dynamic content |
| StaleWhileRevalidate | CSS/JS, semi-dynamic content |
| NetworkOnly | Real-time data, payments |
| CacheOnly | Offline-only content |
// app/offline/page.tsx
export default function OfflinePage() {
return (
<div className="flex flex-col items-center justify-center min-h-screen p-4">
<h1 className="text-2xl font-bold mb-4">You're Offline</h1>
<p className="text-gray-600 text-center mb-6">
Please check your internet connection and try again.
</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-blue-500 text-white rounded-lg"
>
Retry
</button>
</div>
);
}
// hooks/useOnlineStatus.ts
import { useState, useEffect } from 'react';
export function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
setIsOnline(navigator.onLine);
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;
}
// hooks/useInstallPrompt.ts
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(() => {
// Check if already installed
if (window.matchMedia('(display-mode: standalone)').matches) {
setIsInstalled(true);
return;
}
const handleBeforeInstall = (e: Event) => {
e.preventDefault();
setInstallPrompt(e as BeforeInstallPromptEvent);
};
window.addEventListener('beforeinstallprompt', handleBeforeInstall);
return () => {
window.removeEventListener('beforeinstallprompt', handleBeforeInstall);
};
}, []);
const install = async () => {
if (!installPrompt) return;
await installPrompt.prompt();
const { outcome } = await installPrompt.userChoice;
if (outcome === 'accepted') {
setIsInstalled(true);
}
setInstallPrompt(null);
};
return { canInstall: !!installPrompt, isInstalled, install };
}
async function requestNotificationPermission() {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
// Subscribe to push
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY,
});
// Send subscription to server
await fetch('/api/push/subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
headers: { 'Content-Type': 'application/json' },
});
}
}
CRITICAL: When configuring PWA features:
.env.example with placeholders# .env.example
NEXT_PUBLIC_VAPID_PUBLIC_KEY=your_vapid_public_key_here
VAPID_PRIVATE_KEY=your_vapid_private_key_here