🎯

pwa-development

🎯Skill

from travisjneuman/.claude

VibeIndex|
What it does

Develops progressive web apps with offline capabilities, installable interfaces, and cross-platform compatibility using service workers and web manifest.

πŸ“¦

Part of

travisjneuman/.claude(62 items)

pwa-development

Installation

git cloneClone repository
git clone https://github.com/travisjneuman/.claude.git ~/.claude
Install ScriptRun install script
curl -fsSL https://raw.githubusercontent.com/travisjneuman/.claude/master/scripts/install.sh | bash
git cloneClone repository
git clone --recurse-submodules https://github.com/travisjneuman/.claude.git ~/.claude
npxRun with npx
npx vite-bundle-visualizer
npxRun with npx
npx knip
Server ConfigurationMCP server configuration block
{ "mcpServers": { // ─────────────────────────────────────────────────────...
πŸ“– Extracted from docs: travisjneuman/.claude
3Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Progressive Web App development for installable, offline-capable web applications. Use when building PWAs, implementing service workers, or creating offline-first experiences.

Overview

# Progressive Web App (PWA) Development

Build installable, offline-capable web applications that work across all platforms.

PWA Capabilities

| Feature | Support |

| ------------------ | --------------------- |

| Offline access | All modern browsers |

| Install prompt | Chrome, Edge, Samsung |

| Push notifications | All except iOS Safari |

| Background sync | Chrome, Edge |

| File handling | Chrome, Edge |

| Share target | Chrome, Edge, Safari |

---

Core Requirements

1. Web App Manifest

```json

// public/manifest.json

{

"name": "My Progressive Web App",

"short_name": "MyPWA",

"description": "A progressive web 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",

"purpose": "maskable any"

},

{

"src": "/icons/icon-96x96.png",

"sizes": "96x96",

"type": "image/png"

},

{

"src": "/icons/icon-128x128.png",

"sizes": "128x128",

"type": "image/png"

},

{

"src": "/icons/icon-192x192.png",

"sizes": "192x192",

"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 Document",

"short_name": "New",

"url": "/new",

"icons": [{ "src": "/icons/new.png", "sizes": "96x96" }]

}

],

"share_target": {

"action": "/share",

"method": "POST",

"enctype": "multipart/form-data",

"params": {

"title": "title",

"text": "text",

"url": "url",

"files": [

{

"name": "files",

"accept": ["image/", "text/"]

}

]

}

}

}

```

2. HTML Meta Tags

```html

My PWA

```

---

Service Worker

Basic Service Worker

```typescript

// public/sw.js

const CACHE_NAME = "my-pwa-v1";

const STATIC_ASSETS = [

"/",

"/index.html",

"/manifest.json",

"/icons/icon-192x192.png",

];

// Install - cache static assets

self.addEventListener("install", (event) => {

event.waitUntil(

caches.open(CACHE_NAME).then((cache) => {

return cache.addAll(STATIC_ASSETS);

}),

);

self.skipWaiting();

});

// Activate - clean 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 - serve from cache, fallback to network

self.addEventListener("fetch", (event) => {

event.respondWith(

caches.match(event.request).then((cached) => {

// Cache-first strategy

if (cached) {

return cached;

}

// Network fallback

return fetch(event.request).then((response) => {

// Don't cache non-GET or failed requests

if (event.request.method !== "GET" || !response.ok) {

return response;

}

// Cache successful responses

const responseClone = response.clone();

caches.open(CACHE_NAME).then((cache) => {

cache.put(event.request, responseClone);

});

return response;

});

}),

);

});

```

Workbox (Recommended)

```typescript

// src/sw.ts

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

registerRoute(

({ request }) => request.destination === "image",

new CacheFirst({

cacheName: "images",

plugins: [

new ExpirationPlugin({

maxEntries: 100,

maxAgeSeconds: 30 24 60 * 60, // 30 days

}),

],

}),

);

// Network-first for API calls

registerRoute(

({ url }) => url.pathname.startsWith("/api/"),

new NetworkFirst({

cacheName: "api-cache",

plugins: [

new ExpirationPlugin({

maxEntries: 50,

maxAgeSeconds: 5 * 60, // 5 minutes

}),

],

}),

);

// Stale-while-revalidate for pages

registerRoute(

({ request }) => request.mode === "navigate",

new StaleWhileRevalidate({

cacheName: "pages",

}),

);

```

Service Worker Registration

```typescript

// src/registerSW.ts

export async function registerServiceWorker() {

if ("serviceWorker" in navigator) {

try {

const registration = await navigator.serviceWorker.register("/sw.js", {

scope: "/",

});

registration.addEventListener("updatefound", () => {

const newWorker = registration.installing;

if (newWorker) {

newWorker.addEventListener("statechange", () => {

if (newWorker.state === "installed") {

if (navigator.serviceWorker.controller) {

// New update available

dispatchEvent(new CustomEvent("sw:update"));

}

}

});

}

});

console.log("Service Worker registered:", registration.scope);

} catch (error) {

console.error("Service Worker registration failed:", error);

}

}

}

```

---

Install Prompt

Custom Install Button

```tsx

import { useState, useEffect } from "react";

interface BeforeInstallPromptEvent extends Event {

prompt: () => Promise;

userChoice: Promise<{ outcome: "accepted" | "dismissed" }>;

}

function InstallButton() {

const [deferredPrompt, setDeferredPrompt] =

useState(null);

const [isInstalled, setIsInstalled] = useState(false);

useEffect(() => {

// Check if already installed

if (window.matchMedia("(display-mode: standalone)").matches) {

setIsInstalled(true);

return;

}

const handler = (e: Event) => {

e.preventDefault();

setDeferredPrompt(e as BeforeInstallPromptEvent);

};

window.addEventListener("beforeinstallprompt", handler);

return () => window.removeEventListener("beforeinstallprompt", handler);

}, []);

const handleInstall = async () => {

if (!deferredPrompt) return;

await deferredPrompt.prompt();

const { outcome } = await deferredPrompt.userChoice;

if (outcome === "accepted") {

setIsInstalled(true);

}

setDeferredPrompt(null);

};

if (isInstalled || !deferredPrompt) return null;

return (

);

}

```

---

Offline Support

Offline Detection

```tsx

import { useState, useEffect } from "react";

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;

}

// Usage

function App() {

const isOnline = useOnlineStatus();

return (

{!isOnline && (

You're offline. Some features may be unavailable.

)}

);

}

```

Background Sync

```typescript

// In Service Worker

self.addEventListener("sync", (event) => {

if (event.tag === "sync-data") {

event.waitUntil(syncData());

}

});

async function syncData() {

const db = await openDB("pending-requests", 1);

const requests = await db.getAll("requests");

for (const request of requests) {

try {

await fetch(request.url, request.options);

await db.delete("requests", request.id);

} catch {

// Will retry on next sync

}

}

}

// Register sync from app

async function queueRequest(url: string, options: RequestInit) {

await navigator.serviceWorker.ready;

if ("sync" in window.ServiceWorkerRegistration.prototype) {

// Store request and trigger sync

const db = await openDB("pending-requests", 1);

await db.add("requests", { url, options, id: Date.now() });

await navigator.serviceWorker.ready.then((reg) =>

reg.sync.register("sync-data"),

);

} else {

// Fallback to immediate fetch

await fetch(url, options);

}

}

```

---

Push Notifications

Request Permission

```typescript

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: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),

});

// Send subscription to server

await fetch("/api/push/subscribe", {

method: "POST",

body: JSON.stringify(subscription),

});

}

}

```

Handle Push in Service Worker

```typescript

self.addEventListener("push", (event) => {

const data = event.data?.json() ?? {};

event.waitUntil(

self.registration.showNotification(data.title, {

body: data.body,

icon: "/icons/icon-192x192.png",

badge: "/icons/badge.png",

data: data.url,

}),

);

});

self.addEventListener("notificationclick", (event) => {

event.notification.close();

if (event.notification.data) {

event.waitUntil(clients.openWindow(event.notification.data));

}

});

```

---

Vite PWA Plugin

```typescript

// vite.config.ts

import { VitePWA } from "vite-plugin-pwa";

export default defineConfig({

plugins: [

react(),

VitePWA({

registerType: "autoUpdate",

includeAssets: ["favicon.ico", "robots.txt", "icons/*.png"],

manifest: {

name: "My PWA",

short_name: "MyPWA",

theme_color: "#3b82f6",

icons: [

// ... icon definitions

],

},

workbox: {

globPatterns: ["*/.{js,css,html,ico,png,svg,woff2}"],

runtimeCaching: [

{

urlPattern: /^https:\/\/api\.example\.com\/.*/i,

handler: "NetworkFirst",

options: {

cacheName: "api-cache",

expiration: {

maxEntries: 100,

maxAgeSeconds: 60 60 24, // 24 hours

},

},

},

],

},

}),

],

});

```

---

Testing PWA

Lighthouse Audit

  1. Open Chrome DevTools
  2. Go to Lighthouse tab
  3. Select "Progressive Web App"
  4. Run audit

Required Scores

  • Performance: 90+
  • PWA: 100
  • Accessibility: 90+
  • Best Practices: 90+

---

Best Practices

DO:

  • Use HTTPS (required)
  • Provide offline fallback
  • Cache static assets
  • Show update notification
  • Handle all screen sizes

DON'T:

  • Cache everything forever
  • Block app on SW update
  • Ignore iOS limitations
  • Skip manifest icons
  • Forget offline states