Ce que vous saurez faire à la fin
- Comprendre les avantages PWA vs site classique vs app native.
- Configurer un Service Worker pour cache et offline.
- Créer un manifeste PWA installable.
- Activer push notifications, géolocalisation, caméra.
- Tester, déployer et publier sur Play Store via Bubblewrap.
Durée : 2-4 semaines pour PWA complète. Pré-requis : site web HTTPS obligatoire, code source accessible (HTML/CSS/JS ou framework React/Vue/Angular), notions JavaScript, Chrome DevTools, hébergement supportant les Service Workers.
Étape 1 — Comprendre les avantages PWA
| Critère | Site classique | PWA | App native |
|---|---|---|---|
| Installation | Non | Icône écran d’accueil | Store |
| Offline | Non | Oui (Service Worker) | Oui |
| Push notifications | Limité | Oui | Oui |
| Coût dev | 1× | 1,2× | 3-5× |
| App Store | Non | Oui (TWA) | Oui |
| Mises à jour | Auto | Auto | Via Store |
| Performance | Standard | Élevée | Maximum |
Idéal pour : e-commerce, médias, SaaS B2B, services locaux. Limité pour : jeux 3D, app caméra avancée.
Étape 2 — Vérifier les prérequis techniques
Checklist obligatoire :
✓ HTTPS activé (Let's Encrypt gratuit)
✓ Service Worker enregistrable
✓ Web App Manifest présent
✓ Icônes 192x192 et 512x512 minimum
✓ Responsive design (mobile-first)
✓ Performance Lighthouse > 80
Test : Chrome DevTools → Application → Manifest + Service Workers
Étape 3 — Créer le Web App Manifest
Fichier manifest.json à la racine du site :
{
"name": "Mon App PWA",
"short_name": "MonApp",
"description": "Description courte de l'application",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2563EB",
"orientation": "portrait",
"scope": "/",
"lang": "fr-SN",
"icons": [
{"src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable"},
{"src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable"}
],
"shortcuts": [
{"name": "Mon profil", "url": "/profile", "icons": [{"src": "/icons/profile.png", "sizes": "96x96"}]}
]
}
Étape 4 — Lier le manifeste dans HTML
<!-- Dans <head> -->
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#2563EB">
<link rel="apple-touch-icon" href="/icons/icon-192.png">
<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="MonApp">
Étape 5 — Créer un Service Worker basique
Fichier service-worker.js à la racine :
const CACHE_NAME = 'monapp-v1';
const ASSETS = [
'/',
'/styles.css',
'/app.js',
'/icons/icon-192.png',
'/offline.html'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS))
);
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k)))
)
);
self.clients.claim();
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) =>
response || fetch(event.request).catch(() => caches.match('/offline.html'))
)
);
});
Étape 6 — Enregistrer le Service Worker
// Dans app.js ou main.js
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then((reg) => console.log('SW registered:', reg.scope))
.catch((err) => console.error('SW registration failed:', err));
});
}
Étape 7 — Implémenter une stratégie de cache avancée
3 stratégies courantes :
// 1. Cache First (pour assets statiques)
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/static/')) {
event.respondWith(
caches.match(event.request).then(r => r || fetch(event.request))
);
}
});
// 2. Network First (pour API)
async function networkFirst(request) {
try {
const response = await fetch(request);
const cache = await caches.open('api-cache');
cache.put(request, response.clone());
return response;
} catch {
return caches.match(request);
}
}
// 3. Stale While Revalidate (pour images dynamiques)
async function staleWhileRevalidate(request) {
const cache = await caches.open('img-cache');
const cached = await cache.match(request);
const fetched = fetch(request).then(r => { cache.put(request, r.clone()); return r; });
return cached || fetched;
}
Étape 8 — Activer le bouton « Installer l’app »
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
// Afficher votre bouton "Installer l'app"
document.getElementById('install-btn').style.display = 'block';
});
document.getElementById('install-btn').addEventListener('click', async () => {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log(outcome === 'accepted' ? 'Installé' : 'Refusé');
deferredPrompt = null;
});
Étape 9 — Activer les Push Notifications
// Demander permission
async function subscribeUser() {
const permission = await Notification.requestPermission();
if (permission !== 'granted') return;
const reg = await navigator.serviceWorker.ready;
const sub = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: 'YOUR_VAPID_PUBLIC_KEY'
});
// Envoyer la subscription au serveur
await fetch('/api/save-subscription', {
method: 'POST',
body: JSON.stringify(sub),
headers: {'Content-Type': 'application/json'}
});
}
// Recevoir notification dans Service Worker
self.addEventListener('push', (event) => {
const data = event.data.json();
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icons/icon-192.png',
badge: '/icons/badge.png',
data: { url: data.url }
})
);
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
event.waitUntil(clients.openWindow(event.notification.data.url));
});
Étape 10 — Accéder caméra, géolocalisation, partage
// Géolocalisation
navigator.geolocation.getCurrentPosition(
(pos) => console.log(pos.coords),
(err) => console.error(err)
);
// Caméra
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
document.querySelector('video').srcObject = stream;
// Web Share API (partage natif)
if (navigator.share) {
await navigator.share({
title: 'Mon contenu',
url: 'https://monsite.com/article'
});
}
// Notifications badging
if ('setAppBadge' in navigator) {
navigator.setAppBadge(5); // afficher badge "5" sur icône
}
Étape 11 — Auditer avec Lighthouse
Chrome DevTools > Lighthouse > cochez « Progressive Web App » > Run audit. Cibles :
- Performance > 90
- Accessibility > 90
- Best Practices > 90
- SEO > 90
- PWA : tous les critères validés (badge « Installable »)
Étape 12 — Publier sur Google Play Store via Bubblewrap
# Installation
npm install -g @bubblewrap/cli
# Initialisation projet (génère un projet Android Studio)
bubblewrap init --manifest=https://votresite.com/manifest.json
# Build APK / AAB
bubblewrap build
# Créez compte Google Play Developer (25 USD one-time)
# Uploadez l'AAB généré
# Décrivez votre app, screenshots, description
# Publication 2-7 jours pour validation
Avantages : votre PWA dans le Play Store, présence officielle, install via Play Store en 1 clic.
Étape 13 — Mesurer l’usage PWA
Google Analytics 4 events à tracker :
// Quand l'app est installée
window.addEventListener('appinstalled', () => {
gtag('event', 'pwa_installed');
});
// Quand l'app est lancée en mode standalone
if (window.matchMedia('(display-mode: standalone)').matches) {
gtag('event', 'pwa_launch');
}
// Push notification cliquée
self.addEventListener('notificationclick', () => {
gtag('event', 'push_clicked');
});
Étape 14 — Maintenir et faire évoluer
Routine mensuelle :
- Audit Lighthouse (régression performance)
- Vérifier compatibilité nouveaux navigateurs (Safari iOS notamment, support PWA limité)
- Update Service Worker (versionner CACHE_NAME pour forcer refresh)
- Surveiller les KPI : install rate, launch rate, NPS push notifications
- Itérer sur les features les plus utilisées
Erreurs classiques en PWA
- Pas de HTTPS : Service Worker refuse de s’enregistrer.
- Service Worker mal versionné : users coincés sur ancienne version.
- Cache trop agressif : nouveautés invisibles, frustration.
- Push notifications spam : users désabonnent, marque ternie.
- iOS limited support : testez tout sur Safari iOS, surprises possibles.
Checklist PWA complète
✓ HTTPS actif
✓ Manifest JSON complet et lié
✓ Icônes 192 et 512 (any maskable)
✓ Service Worker enregistré
✓ Stratégie de cache (CF, NF, SWR) selon ressource
✓ Page offline.html fallback
✓ Bouton "Installer l'app" actif
✓ Push notifications avec VAPID + backend
✓ Web Share / Camera / Geo si pertinent
✓ Lighthouse PWA score 100
✓ Publié sur Play Store via Bubblewrap
✓ Analytics PWA events trackés
✓ Test régulier Safari iOS