Skip to main content

pwa

Use when the user asks to make a site installable, turn it into a PWA, add a service worker, add a web app manifest, enable "add to home screen", or support offline. Also use when adding favicon/app icons, iOS home-screen support (apple-mobile-web-app meta tags), or verifying PWA installability.

Aller à l'installation

Informations de source

Dépôt
spinspire/pocketbase-sveltekit-starter
Dernière activité de la source
21 août 2026 à 19:18
Langue détectée de SKILL.md
anglais
Étoiles
509
Forks
76

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
pwa
description
Use when the user asks to make a site installable, turn it into a PWA, add a service worker, add a web app manifest, enable "add to home screen", or support offline. Also use when adding favicon/app icons, iOS home-screen support (apple-mobile-web-app meta tags), or verifying PWA installability.
license
MIT
authors
SpinSpire Team
# PWA Installability Make a web app installable and iOS-friendly with a minimal service worker. Default posture is **network-only** (no caching) unless the app needs offline — pick a strategy deliberately, never by default. ## When to Use - "Make it installable / a PWA" - "Add a service worker" / "add a web manifest" / "add to home screen" - "Works offline?" or "works on iPhone?" ## The Three Pillars 1. **HTTPS** — required for service workers (localhost exempt in dev). 2. **Web app manifest** — name, icons, start_url, display → makes it installable. 3. **Service worker** — background script; active SW + valid manifest unlocks the install prompt. ## Quick Reference | Asset | Where it goes | Notes | |-------|---------------|-------| | `manifest.webmanifest` | site root (`static/` for SvelteKit) | name, short_name, start_url, scope, display `standalone`, theme/background color, icons 192+512 + maskable | | `favicon-32/48/64.png` | root | browser tab icons; also `apple-touch-icon.png` 180px for iOS | | `icon-192.png`, `icon-512.png` | root | required by Chrome for install | | `icon-maskable-512.png` | root | full-bleed background, content in center 80% safe zone | | `sw.js` | root (`static/` for SvelteKit) | register from client code; served at site root for correct scope | | `offline.html` (optional) | root | fallback for failed navigations if offline support is wanted | ## Service Worker ### Default: network-only (no caching) ```js self.addEventListener('install', () => { self.skipWaiting(); }); self.addEventListener('activate', (event) => { event.waitUntil(clients.claim()); }); self.addEventListener('fetch', (event) => { event.respondWith(fetch(event.request)); }); ``` This makes the app installable and always-fresh. Use for apps where data lives server-side / in a spreadsheet and serving stale data is wrong. ### Offline fallback (add only if offline support is requested) ```js const CACHE = 'capstone-v1'; self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE).then((c) => c.addAll(['/', '/offline.html', '/manifest.webmanifest', '/icon-192.png', '/icon-512.png'])) ); self.skipWaiting(); }); self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))).then(() => clients.claim()) ); }); self.addEventListener('fetch', (event) => { const url = new URL(event.request.url); if (event.request.mode === 'navigate') { event.respondWith(fetch(event.request).catch(() => caches.match('/offline.html'))); return; } if (url.origin === location.origin && ['css', 'js', 'svg', 'png', 'jpg', 'woff2'].some((ext) => url.pathname.endsWith('.' + ext))) { event.respondWith(caches.match(event.request).then((cached) => cached || fetch(event.request).then((res) => { const clone = res.clone(); caches.open(CACHE).then((c) => c.put(event.request, clone)); return res; }))); return; } event.respondWith(fetch(event.request)); }); ``` ## Caching strategy decision table | Strategy | Behavior | Use for | |----------|----------|---------| | **Network only** | Always hit network, never cache | Auth, live/volatile data, spreadsheets, anything where stale is wrong. **Default.** | | **Cache first** | Serve from cache, update in background | Static assets: css/js, fonts, images | | **Network first** | Try network, fall back to cache | HTML pages, API reads where offline read > error | | **Stale while revalidate** | Serve stale instantly, refresh cache | Semi-static content, lists | When caching, version the cache name and delete old caches in `activate`. Never cache `POST` or auth'd API responses. ## Registration (client side) Register on mount, not at parse time, so it never delays first paint: ```js if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').catch((err) => console.warn('SW registration failed', err)); } ``` In SvelteKit, put this in `+layout.svelte` `onMount`. The SW must be at the site root (`/sw.js`) — `static/sw.js` ships there via adapter-static. ## Manifest ```json { "name": "Capstone — Construction business operations", "short_name": "Capstone", "description": "One-line description.", "start_url": "/", "scope": "/", "display": "standalone", "background_color": "#faf9f6", "theme_color": "#1f2933", "icons": [ { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }, { "src": "/icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } ] } ``` Link it in the HTML head: `<link rel="manifest" href="/manifest.webmanifest" />`. ## Icons from source images - Source should be square (e.g. 768×768). Crop/resize with ImageMagick: ```bash magick src.png -resize 192x192 icon-192.png magick src.png -resize 512x512 icon-512.png magick -size 512x512 xc:"#1f2933" \( src.png -resize 60% +repage \) -gravity center -composite icon-maskable-512.png ``` - Maskable icons need a full-bleed background with the logo at ~60% inside the center safe zone. ## iOS support (Safari / Add to Home Screen) iOS ignores `theme_color` and needs legacy meta tags: ```html <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-title" content="AppName" /> <meta name="theme-color" content="#1f2933" /> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> ``` Caveats: - iOS has no `beforeinstallprompt` — users install via **Share → Add to Home Screen**. - iOS may evict service workers after ~7 days of inactivity (Intelligent Tracking Prevention) — expect re-registration on next visit. ## Pre-ship audit 1. `bun run check` (or project typecheck) and build pass. 2. Start dev server, then verify with agent-browser: - manifest + SW reachable: `curl -s -o /dev/null -w "%{http_code}" localhost:5173/manifest.webmanifest` → 200 - all icon assets return 200 (favicon-32/48/64, apple-touch-icon, icon-192/512, icon-maskable-512, sw.js) - SW registered + active: ``` agent-browser eval "navigator.serviceWorker.getRegistration().then(r => ({ scope: r?.scope, active: r?.active?.state }))" ``` - links present in head: `rel=icon`, `rel=apple-touch-icon`, `rel=manifest`, `meta[name=theme-color]` 3. Production requires HTTPS (Cloudflare Pages et al. provide it). Local dev on `http://localhost` is exempt for SW registration. 4. Optional: run Lighthouse PWA audit in Chrome DevTools. ## Common Mistakes - **Registering SW in `<head>` or at module top-level** — delays first paint; use onMount. - **Caching auth'd or volatile API responses** — serves stale or broken data. Keep those network-only. - **SW not at root** — `/sw.js` only controls the scope beneath it; a SW in `/js/sw.js` won't control `/`. - **Skipping maskable icon** — Android adaptive icons will crop the logo without one. - **Cache name never versioned / old caches never deleted** — disk fills and stale assets linger. - **Forgetting iOS meta tags** — app opens in Safari tab instead of standalone, wrong status bar.
Voir sur GitHub