Synthèse automatique
Ce skill décrit des motifs d’implémentation de service workers en JavaScript natif pour le support hors ligne, la mise en cache et les fonctionnalités PWA.
Points forts
- Couvre l’enregistrement du service worker et la gestion des mises à jour avec des événements personnalisés.
- Fournit un modèle complet avec précache, versionnage des caches et nettoyage des anciennes versions.
- Privilégie le JavaScript natif, sans dépendance à Workbox.
- Inclut des journaux et un parcours de mise à jour explicite pour faciliter le débogage.
Limites
- Reste centré sur des motifs en JavaScript natif et n’aborde pas les intégrations Workbox ou les plugins PWA de frameworks.
- L’expérience de mise à jour, comme l’invite et le rechargement, est laissée à l’application.
- Ne fournit pas de recettes détaillées pour la synchronisation en arrière-plan ou les notifications push.
Pour ajouter un support hors ligne, un précache et un parcours de mise à jour à une PWA ou à un site en JavaScript natif.
À éviter si le projet utilise déjà Workbox, un plugin PWA de framework ou a besoin de recettes prêtes pour la synchronisation en arrière-plan et les notifications push.
Analyse de sécurité
SûrThe skill is purely educational documentation about vanilla service worker patterns (registration, caching strategies, precaching, update handling). It contains no shell commands, no network exfiltration, no obfuscated payloads, and no instructions to disable safeguards. Declared tools are limited to Read/Write/Edit, and all code samples are ordinary client-side JavaScript confined to the browser's service worker API. The only theoretical concern—caching and skipWaiting behavior—is presented with cautious defaults (deferred skipWaiting, versioned cache names, cross-origin requests skipped), which is good practice rather than a risk.
Aucun point d'attention détecté
Comment cette analyse est faiteExemples
Add a service worker to my vanilla JavaScript site that precaches the core HTML, CSS, JS, and an offline fallback page.Implement a service worker update prompt that detects a new version, lets the user accept it, and reloads the page when the new worker takes control.Review my sw.js cache versioning and make sure old app-cache entries are cleaned up during activation.name: service-worker description: Service worker patterns for offline support, caching strategies, and PWA functionality. Use when implementing offline-first features, caching, or background sync. allowed-tools: Read, Write, Edit
Service Worker Skill
This skill covers service worker implementation patterns for offline support, intelligent caching, and Progressive Web App (PWA) functionality—without relying on Workbox or other abstractions.
Philosophy
Service workers should be:
- Progressive - Enhance, don't break; site works without SW
- Predictable - Clear caching strategies with versioned caches
- Debuggable - Logging and clear update paths
- Lightweight - Vanilla JS, no frameworks required
File Structure
src/
├── sw.js # Main service worker
├── sw-assets.js # Asset list for precaching (optional)
└── js/
└── sw-register.js # Registration script
Service Worker Registration
Basic Registration
// js/sw-register.js
/**
* Register service worker with update handling
*/
export async function registerServiceWorker() {
if (!('serviceWorker' in navigator)) {
console.log('Service workers not supported');
return null;
}
try {
const registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/',
});
console.log('SW registered:', registration.scope);
// Check for updates on page load
registration.update();
// Handle updates
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// New version available
dispatchEvent(new CustomEvent('sw-update-available', {
detail: { registration },
}));
}
});
});
return registration;
} catch (error) {
console.error('SW registration failed:', error);
return null;
}
}
/**
* Skip waiting and reload when update is accepted
* @param {ServiceWorkerRegistration} registration
*/
export function acceptUpdate(registration) {
if (registration.waiting) {
registration.waiting.postMessage({ type: 'SKIP_WAITING' });
}
// Reload once the new SW takes control
navigator.serviceWorker.addEventListener('controllerchange', () => {
window.location.reload();
});
}
Registration in HTML
<script type="module">
import { registerServiceWorker } from '/js/sw-register.js';
registerServiceWorker();
// Optional: Show update prompt
window.addEventListener('sw-update-available', (event) => {
if (confirm('New version available. Reload?')) {
acceptUpdate(event.detail.registration);
}
});
</script>
Service Worker Template
Complete SW Structure
// sw.js
const CACHE_VERSION = 'v1.0.0';
const CACHE_NAME = `app-cache-${CACHE_VERSION}`;
// Assets to precache on install
const PRECACHE_ASSETS = [
'/',
'/index.html',
'/styles/main.css',
'/js/app.js',
'/offline.html',
];
// ==================== INSTALL ====================
self.addEventListener('install', (event) => {
console.log('[SW] Installing version:', CACHE_VERSION);
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(PRECACHE_ASSETS))
.then(() => {
console.log('[SW] Precache complete');
// Don't skip waiting by default - let user decide
})
);
});
// ==================== ACTIVATE ====================
self.addEventListener('activate', (event) => {
console.log('[SW] Activating version:', CACHE_VERSION);
event.waitUntil(
// Clean up old caches
caches.keys()
.then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name.startsWith('app-cache-') && name !== CACHE_NAME)
.map((name) => {
console.log('[SW] Deleting old cache:', name);
return caches.delete(name);
})
);
})
.then(() => {
// Take control of all pages immediately
return self.clients.claim();
})
);
});
// ==================== FETCH ====================
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// Skip non-GET requests
if (request.method !== 'GET') {
return;
}
// Skip cross-origin requests
if (url.origin !== self.location.origin) {
return;
}
// Choose caching strategy based on request type
if (isNavigationRequest(request)) {
event.respondWith(networkFirstWithOffline(request));
} else if (isStaticAsset(url)) {
event.respondWith(cacheFirst(request));
} else if (isApiRequest(url)) {
event.respondWith(networkFirst(request));
} else {
event.respondWith(staleWhileRevalidate(request));
}
});
// ==================== MESSAGE ====================
self.addEventListener('message', (event) => {
if (event.data?.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
// ==================== HELPERS ====================
function isNavigationRequest(request) {
return request.mode === 'navigate';
}
function isStaticAsset(url) {
return /\.(css|js|woff2?|ttf|eot|ico|png|jpe?g|gif|svg|webp|avif)$/.test(url.pathname);
}
function isApiRequest(url) {
return url.pathname.startsWith('/api/');
}
// ==================== STRATEGIES ====================
// ... (strategies defined below)
Caching Strategies
Cache First (Static Assets)
Best for: CSS, JS, fonts, images—things that rarely change.
/**
* Cache first, falling back to network
* @param {Request} request
* @returns {Promise<Response>}
*/
async function cacheFirst(request) {
const cached = await caches.match(request);
if (cached) {
return cached;
}
try {
const response = await fetch(request);
// Cache successful responses
if (response.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
}
return response;
} catch (error) {
console.error('[SW] Cache first failed:', error);
throw error;
}
}
Network First (API, Dynamic Content)
Best for: API responses, user data, frequently updated content.
/**
* Network first, falling back to cache
* @param {Request} request
* @returns {Promise<Response>}
*/
async function networkFirst(request) {
try {
const response = await fetch(request);
// Cache successful responses
if (response.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
}
return response;
} catch (error) {
const cached = await caches.match(request);
if (cached) {
console.log('[SW] Serving from cache:', request.url);
return cached;
}
throw error;
}
}
Network First with Offline Fallback (Navigation)
Best for: HTML pages—show offline page when network fails.
/**
* Network first with offline fallback for navigation
* @param {Request} request
* @returns {Promise<Response>}
*/
async function networkFirstWithOffline(request) {
try {
const response = await fetch(request);
// Cache the page
if (response.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
}
return response;
} catch (error) {
// Try cached version of this page
const cached = await caches.match(request);
if (cached) {
return cached;
}
// Fall back to offline page
const offlinePage = await caches.match('/offline.html');
if (offlinePage) {
return offlinePage;
}
// Last resort: simple offline response
return new Response('Offline', {
status: 503,
statusText: 'Service Unavailable',
headers: { 'Content-Type': 'text/plain' },
});
}
}
Stale While Revalidate (General Content)
Best for: Content that changes but stale is acceptable—blog posts, product pages.
/**
* Return cached immediately, update cache in background
* @param {Request} request
* @returns {Promise<Response>}
*/
async function staleWhileRevalidate(request) {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(request);
// Fetch in background regardless
const fetchPromise = fetch(request)
.then((response) => {
if (response.ok) {
cache.put(request, response.clone());
}
return response;
})
.catch(() => null);
// Return cached immediately, or wait for network
return cached || fetchPromise;
}
Strategy Selection Guide
| Content Type | Strategy | Reason | |-------------|----------|--------| | CSS, JS, fonts | Cache First | Versioned, rarely changes | | Images | Cache First | Large files, cache aggressively | | HTML pages | Network First + Offline | Fresh content, offline fallback | | API responses | Network First | Dynamic data, cache as backup | | Blog/articles | Stale While Revalidate | Fast response, background update | | User avatars | Stale While Revalidate | Changes occasionally |
Offline Page
Create a simple offline fallback:
<!-- offline.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Offline</title>
<style>
body {
font-family: system-ui, sans-serif;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
background: #f3f4f6;
color: #374151;
}
.offline-message {
text-align: center;
padding: 2rem;
}
h1 { margin: 0 0 1rem; }
button {
margin-top: 1rem;
padding: 0.75rem 1.5rem;
font-size: 1rem;
cursor: pointer;
}
</style>
</head>
<body>
<div class="offline-message">
<h1>You're offline</h1>
<p>Please check your internet connection and try again.</p>
<button onclick="location.reload()">Retry</button>
</div>
</body>
</html>
Cache Versioning
Version Bump Strategy
// sw.js
// Increment when precached assets change
const CACHE_VERSION = 'v1.2.0';
// Or use build hash
const CACHE_VERSION = 'v1-abc123';
// Cache name includes version
const CACHE_NAME = `app-cache-${CACHE_VERSION}`;
Automatic Version from Build
// Generated by build process
// sw-version.js
export const SW_VERSION = '2024-01-15T10:30:00Z';
// sw.js
import { SW_VERSION } from './sw-version.js';
const CACHE_NAME = `app-cache-${SW_VERSION}`;
Common Pitfalls
1. Not Handling Updates
Problem: Users stuck on old version.
// BAD: Skip waiting immediately
self.addEventListener('install', () => {
self.skipWaiting(); // Dangerous!
});
// GOOD: Let user trigger update
self.addEventListener('message', (event) => {
if (event.data?.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
2. Caching Error Responses
Problem: Caching 404 or 500 responses.
// BAD: Cache any response
cache.put(request, response.clone());
// GOOD: Only cache successful responses
if (response.ok) {
cache.put(request, response.clone());
}
3. Cache Size Growth
Problem: Cache grows indefinitely.
// Add cache size limits
const MAX_CACHE_SIZE = 50;
async function trimCache(cacheName, maxSize) {
const cache = await caches.open(cacheName);
const keys = await cache.keys();
if (keys.length > maxSize) {
// Delete oldest entries
const toDelete = keys.slice(0, keys.length - maxSize);
await Promise.all(toDelete.map((key) => cache.delete(key)));
}
}
4. Forgetting to Handle POST/PUT
Problem: SW intercepts non-GET requests.
// GOOD: Skip non-GET requests
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') {
return; // Let browser handle it
}
// ... caching logic
});
5. Not Cleaning Old Caches
Problem: Old caches accumulate.
// GOOD: Clean up in activate
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((names) => {
return Promise.all(
names
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
})
);
});
Debugging
Console Logging Pattern
const DEBUG = true;
function log(...args) {
if (DEBUG) {
console.log('[SW]', ...args);
}
}
// Usage
log('Fetch:', request.url);
log('Strategy: cache-first');
log('Cache hit:', !!cached);
DevTools Checklist
-
Application > Service Workers
- Check registration status
- "Update on reload" for development
- "Bypass for network" to test without SW
-
Application > Cache Storage
- Verify cached assets
- Check cache versions
- Delete caches to test fresh install
-
Network tab
- "(from ServiceWorker)" indicates cached response
- Check which requests are intercepted
Force Update During Development
// In DevTools console
navigator.serviceWorker.getRegistration().then((reg) => {
reg.unregister().then(() => {
console.log('SW unregistered');
location.reload();
});
});
Manifest Integration
Service workers work with manifest.webmanifest for full PWA support:
<head>
<link rel="manifest" href="/manifest.webmanifest"/>
</head>
{
"name": "My App",
"short_name": "App",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2563eb",
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }
]
}
Background Sync (Optional)
Queue failed requests for retry when online:
// In main app
async function sendData(data) {
try {
await fetch('/api/data', {
method: 'POST',
body: JSON.stringify(data),
});
} catch (error) {
// Queue for background sync
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('sync-data');
// Store data in IndexedDB for later
}
}
// In sw.js
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-data') {
event.waitUntil(syncStoredData());
}
});
async function syncStoredData() {
// Retrieve from IndexedDB and POST
}
Checklist
When implementing a service worker:
- [ ] Registration handles errors gracefully
- [ ] Update mechanism notifies users
- [ ] CACHE_VERSION incremented with asset changes
- [ ] Old caches cleaned in activate event
- [ ] Only GET requests are intercepted
- [ ] Only successful responses are cached
- [ ] Offline fallback page exists and is precached
- [ ] Appropriate strategy per content type
- [ ] No
skipWaiting()without user consent - [ ] Cache size limits prevent unbounded growth
- [ ] Cross-origin requests are not cached
- [ ] Debug logging available for development
Related Skills
- data-storage - Implement client-side data storage with localStorage, Ind...
- performance - Write performance-friendly HTML pages
- api-client - Fetch API patterns with error handling, retry logic, and ...
- progressive-enhancement - HTML-first development with CSS-only interactivity patterns
Du fichier au premier essai
Le téléchargement contient exactement le contenu affiché sur cette fiche. Cette copie peut différer de la dernière version du dépôt source.
Consulter la source et ses fichiers annexesCe téléchargement contient uniquement SKILL.md. Les scripts, références, modèles et dépendances éventuels ne sont pas inclus. Récupérez les fichiers nécessaires depuis la source en conservant leur arborescence avant de poursuivre.
1. Télécharger le fichier
Télécharger SKILL.mdDepuis la racine d’un projet de test, enregistrez le fichier sous .skill-downloads/service-worker-skills-ms1aw4du/SKILL.md, ou utilisez la commande suivante. Ce dossier sert à examiner la skill avant son installation.
Commande pour macOS/Linux ou WSL : un shell POSIX et curl sont nécessaires. Sous Windows sans WSL, utilisez le bouton de téléchargement et créez le dossier manuellement.
mkdir -p '.skill-downloads/service-worker-skills-ms1aw4du' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/service-worker-skills-ms1aw4du/download' --output '.skill-downloads/service-worker-skills-ms1aw4du/SKILL.md.part' && mv '.skill-downloads/service-worker-skills-ms1aw4du/SKILL.md.part' '.skill-downloads/service-worker-skills-ms1aw4du/SKILL.md'2. Vérifier les prérequis
Lisez SKILL.md avant de l’utiliser. Si le fichier cite des scripts ou services externes, examinez-les et préparez les accès nécessaires dans votre environnement de test.
Outils déclarés par l’auteur (à vérifier dans votre agent) : Read, Write, Edit
Aucune section de prérequis reconnue dans ce fichier. Cela ne signifie pas qu’il est autonome : les bibliothèques, outils et accès peuvent être cités ailleurs dans le contenu ou dans le dépôt source.
Les plateformes de la fiche sont des indications du catalogue. Les chemins ci-dessous suivent la documentation des outils ; ils ne prouvent pas que cette skill y a été exécutée. Aucune version minimale de ces outils n’est attestée pour cette fiche.
3. Charger la skill
Après vérification, copiez le dossier complet dans le projet avec l’une des commandes ci-dessous, puis ouvrez une nouvelle session de l’outil. La commande s’arrête si un dossier du même nom existe déjà : comparez les fichiers avant de le remplacer.
Claude Code
test -f '.skill-downloads/service-worker-skills-ms1aw4du/SKILL.md' && test -s '.skill-downloads/service-worker-skills-ms1aw4du/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/service-worker' && cp -R '.skill-downloads/service-worker-skills-ms1aw4du' '.claude/skills/service-worker'Cursor
test -f '.skill-downloads/service-worker-skills-ms1aw4du/SKILL.md' && test -s '.skill-downloads/service-worker-skills-ms1aw4du/SKILL.md' && mkdir -p '.cursor/skills' && test ! -e '.cursor/skills/service-worker' && cp -R '.skill-downloads/service-worker-skills-ms1aw4du' '.cursor/skills/service-worker'Codex
test -f '.skill-downloads/service-worker-skills-ms1aw4du/SKILL.md' && test -s '.skill-downloads/service-worker-skills-ms1aw4du/SKILL.md' && mkdir -p '.agents/skills' && test ! -e '.agents/skills/service-worker' && cp -R '.skill-downloads/service-worker-skills-ms1aw4du' '.agents/skills/service-worker'OpenSkills
OpenSkills doit déjà être installé. La procédure locale install/read est contrôlée avec OpenSkills 1.5.0, qui exige Node.js ≥ 20.6.0. Aucun slug du catalogue n’est résolu automatiquement.
Installation officielle d’OpenSkillsopenskills install './.skill-downloads/service-worker-skills-ms1aw4du'openskills read 'service-worker-skills-ms1aw4du'La lecture doit afficher le contenu du fichier installé et son dossier de base. Elle ne lance ni la skill ni ses dépendances.
Pour un agent qui lit AGENTS.md, synchronisez ensuite l’index. Cette commande modifie AGENTS.md : relisez le diff avant de l’utiliser.
openskills syncMode manuel ou autre outil : joignez SKILL.md à une conversation, ou ouvrez-le dans un agent capable de lire les fichiers. Ajoutez les ressources annexes nécessaires. Il n’existe pas de dossier universel reconnu par tous les outils.
4. Faire un premier essai
Utilisez une entrée courte et fictive : un endpoint d’API, un petit document ou un brief d’interface. Dans une nouvelle conversation, joignez le fichier ou donnez son chemin à l’agent, puis adaptez cette demande :
Lis .skill-downloads/service-worker-skills-ms1aw4du/SKILL.md et applique ses instructions à cet exemple : [décrire une entrée de test]. Indique les fichiers, dépendances ou accès qui manquent. Produis le résultat demandé par la skill sans inventer de données ni exécuter de script non examiné.Résultat attendu : une sortie correspondant aux instructions de la skill, ou une liste précise des prérequis manquants. Une simple confirmation de lecture ne valide pas le premier usage. Si une ressource externe est indisponible, ce parcours reste à tester.
Bonnes pratiques React Hook Form
Developpement
Maîtrisez React Hook Form : évitez les anti-patterns, gérez les abonnements, et traitez correctement reset, dirty state et entrées contrôlées.
Migration Emotion vers Mantine
Developpement
Migrer les styled-components Emotion vers des composants Mantine avec style props et modules CSS. Pour convertir .styled.tsx ou supprimer les imports @emotion.
Détecteur de dérive de périmètre
Developpement
Analyse les diffs git par rapport à une intention pour détecter la dérive de périmètre, fichiers non liés et PR trop larges, puis suggère de garder, diviser ou justifier.