Une Progressive Web App, ou PWA, reste un site web : les utilisateurs l’ouvrent depuis une URL et les moteurs de recherche peuvent découvrir ses pages publiques. La différence est que le navigateur peut l’installer, l’ouvrir dans une fenêtre autonome et laisser un service worker assurer un fonctionnement hors ligne ou plus résilient. Nous concevons l’installation, le cache et les mises à jour comme des éléments de l’architecture produit, pas comme un plugin activé à la fin.

Comment fonctionne une Progressive Web App ?

L’application visible utilise du HTML, du CSS et du JavaScript classiques. React peut afficher l’interface dans le navigateur, tandis que Next.js peut combiner routes rendues côté serveur, React Server Components et interactions côté client. Un web app manifest fournit les informations d’installation : nom, icônes, URL de démarrage, couleur du thème et mode d’affichage autonome. HTTPS protège la connexion et déverrouille les capacités nécessitant un contexte sécurisé.

Le service worker est un worker JavaScript distinct enregistré par la page. Après son installation et son activation, il peut s’interposer entre l’application et le réseau, répondre depuis un cache, récupérer une réponse récente ou combiner les deux stratégies. Il n’accède pas directement au DOM. La page, le service worker, Cache Storage et l’éventuelle base IndexedDB sont des couches séparées qui communiquent par événements et messages.

Architecture PWA et cycle de vie du service worker

Lors de la première visite, le navigateur télécharge la page normalement. L’application enregistre le service worker, ce qui déclenche les événements install et activate. Pendant l’installation, il peut précacher un petit shell applicatif. À l’activation, il peut supprimer les anciens caches. Dès qu’il contrôle une page, les événements fetch lui permettent de choisir le réseau, une réponse en cache ou un fallback pour chaque requête.

Les mises à jour sont volontairement prudentes. Un nouveau worker téléchargé attend généralement tant qu’une ancienne version contrôle des onglets ouverts, afin d’éviter deux versions incompatibles. Nous prévoyons une expérience explicite : informer l’utilisateur, lui permettre de terminer son travail puis recharger lorsque le nouveau worker est prêt. Forcer l’activation sans coordonner l’état peut supprimer une saisie non enregistrée.

Comment créer une PWA React avec Vite

Pour une application React et Vite, nous générons généralement le manifest et le service worker de production avec vite-plugin-pwa, qui utilise Workbox pour le précache et le cache d’exécution. Installez le package comme dépendance de développement, ajoutez VitePWA aux plugins et définissez le manifest dans vite.config.ts. Les icônes doivent exister aux URL déclarées, notamment dans des variantes PNG de 192 × 192 et 512 × 512 adaptées.

La configuration suivante crée une PWA React installable et demande à l’application de confirmer avant d’appliquer une mise à jour du service worker. Le fallback de navigation convient à une application React Router côté client dont le serveur renvoie le shell pour les routes valides. Un projet multipage ou rendu côté serveur nécessite une stratégie de routage et de cache correspondant à ses véritables réponses HTML.

vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
  plugins: [
    react(),
    VitePWA({
      registerType: 'prompt',
      manifest: {
        name: 'Ma PWA React',
        short_name: 'MaPWA',
        start_url: '/',
        display: 'standalone',
        background_color: '#ffffff',
        theme_color: '#006b63',
        icons: [
          { src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
          { src: '/icon-512.png', sizes: '512x512', type: 'image/png' }
        ]
      },
      workbox: { navigateFallback: '/index.html' }
    })
  ]
})

Enregistrer le service worker dans React

Un assistant généré peut gérer l’enregistrement et les mises à jour, mais comprendre l’API native reste utile. L’enregistrement s’exécute uniquement dans le navigateur, généralement après le chargement de la page. Le fichier du service worker doit être servi depuis la portée qu’il doit contrôler : un worker placé à /sw.js peut contrôler l’origine, tandis qu’un fichier dans /assets/ possède par défaut une portée plus réduite.

Considérez l’échec de l’enregistrement comme celui d’une amélioration progressive. L’application en ligne doit continuer à fonctionner si les service workers ne sont pas pris en charge ou sont bloqués. Journalisez les erreurs en développement, exposez l’état de mise à jour dans l’interface et testez le build de production installé au lieu de supposer qu’un enregistrement réussi garantit un bon mode hors ligne.

src/main.tsx
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      const registration = await navigator.serviceWorker.register('/sw.js')
      console.info('Portée du service worker :', registration.scope)
    } catch (error) {
      console.error('Échec de l’enregistrement du service worker', error)
    }
  })
}

Créer le manifest d’une PWA Next.js avec l’App Router

L’App Router de Next.js prend nativement en charge un fichier de métadonnées manifest typé. Créez app/manifest.ts et retournez un objet MetadataRoute.Manifest. Next.js l’expose comme web app manifest : le nom, la description, les icônes et le comportement d’affichage restent donc dans le code de l’application. Un fichier statique app/manifest.json convient aussi lorsque les valeurs ne changent jamais.

Conservez start_url dans la portée du déploiement et choisissez volontairement le purpose des icônes. Une icône maskable contient une marge de sécurité afin que les systèmes puissent la recadrer sous différentes formes. Les couleurs du thème et de l’arrière-plan doivent correspondre au premier écran peint pour éviter une transition brutale lors du lancement de l’application installée.

app/manifest.ts
import type { MetadataRoute } from 'next'

export default function manifest(): MetadataRoute.Manifest {
  return {
    name: 'Ma PWA Next.js',
    short_name: 'NextPWA',
    description: 'Une Progressive Web App installable créée avec Next.js',
    start_url: '/',
    display: 'standalone',
    background_color: '#ffffff',
    theme_color: '#006b63',
    icons: [
      { src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
      { src: '/icon-512.png', sizes: '512x512', type: 'image/png' }
    ]
  }
}

Ajouter un service worker et le cache à Next.js

Un service worker Next.js simple peut être placé dans public/sw.js puis enregistré par un petit composant client. Pour un véritable mode hors ligne en production, nous préférons un outil maintenu comme Workbox ou une intégration Next.js telle que Serwist, capable de générer une liste de précache correcte pour les assets hachés. L’équipe doit toujours concevoir la politique de cache : la génération ne décide pas quelles données métier peuvent être stockées.

Utilisez cache-first pour les assets statiques versionnés, stale-while-revalidate pour le contenu pouvant accepter une courte obsolescence et network-first pour les navigations qui doivent rester fraîches tout en disposant d’un fallback hors ligne. Ne mettez pas en cache globalement les pages authentifiées, les réponses API, les paiements ou le HTML personnalisé. Les réponses serveur Next.js peuvent varier selon les cookies, les en-têtes et les règles de revalidation.

public/sw.js
const CACHE = 'app-shell-v1'
const APP_SHELL = ['/', '/offline', '/icon-192.png']

self.addEventListener('install', event => {
  event.waitUntil(caches.open(CACHE).then(cache => cache.addAll(APP_SHELL)))
})

self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(keys => Promise.all(
      keys.filter(key => key !== CACHE).map(key => caches.delete(key))
    ))
  )
})

self.addEventListener('fetch', event => {
  if (event.request.mode !== 'navigate') return
  event.respondWith(
    fetch(event.request).catch(() => caches.match('/offline'))
  )
})

Concevoir les données hors ligne, l’installation et les mises à jour

Une interface hors ligne et des données hors ligne sont deux problèmes différents. Cache Storage convient aux objets Request et Response comme les scripts, styles, images et certaines pages. IndexedDB convient mieux aux brouillons structurés, à une boîte d’envoi et aux enregistrements à synchroniser. Si les utilisateurs modifient des données hors ligne, définissez les conflits, l’état de synchronisation visible et les nouvelles tentatives avant de construire l’interface.

La prise en charge de l’installation varie selon le navigateur et la plateforme : ne masquez jamais une fonction essentielle derrière un bouton d’installation. Détectez le mode standalone, fournissez des instructions adaptées à la plateforme et considérez l’événement d’installation du navigateur comme une amélioration. Lorsqu’une mise à jour est disponible, expliquez son effet et rechargez seulement après l’accord de l’utilisateur ou à un moment sûr.

Règles de sécurité et de performance pour une PWA

Servez la PWA de production en HTTPS et gardez le service worker léger. Limitez le cache aux requêtes GET de même origine, sauf besoin révisé. Ne placez jamais de secrets dans le manifest, le bundle client ou le service worker. Validez les données des notifications push, demandez la permission après une action utilisateur explicite et évitez de solliciter les notifications dès la première page.

Ne précachez que les assets nécessaires au démarrage. Un précache trop volumineux ralentit la première installation, consomme du stockage et augmente le risque qu’une requête échouée bloque l’événement install. Ajoutez des limites et des expirations aux images ou documents mis en cache à l’exécution. Mesurez le premier chargement non mis en cache sur une vraie connexion mobile : la PWA doit être un site rapide avant de devenir une application installée.

Tester et déployer une PWA React ou Next.js

Testez un build de production sur HTTPS ou localhost. Dans les outils de développement du navigateur, inspectez le manifest, les icônes, l’état du service worker et Cache Storage. Vérifiez le premier chargement, les visites suivantes, le lancement hors ligne, un réseau lent, l’expiration du cache et la mise à niveau depuis le worker précédemment déployé. Testez directement les routes profondes, car une page d’accueil en cache peut masquer un routage serveur cassé.

Lancez des audits d’accessibilité et de performance sans considérer un seul score Lighthouse comme la décision finale. Installez la PWA sur Android, iOS et une plateforme desktop utilisée par le public. Contrôlez le clavier, les safe areas, les couleurs du thème, l’orientation, le partage et la récupération après suppression du stockage. Surveillez les erreurs du service worker et l’adoption des versions après le déploiement.

PWA React ou PWA Next.js : que choisir ?

Choisissez React avec Vite lorsque le produit est surtout une application côté client, que l’hébergement statique est utile et que l’équipe souhaite contrôler directement le routage et les frontières API. Choisissez Next.js lorsque les routes publiques profitent du rendu serveur, des métadonnées, du chargement de données par route ou de fonctions serveur. Les deux permettent une excellente PWA installable ; le bon choix dépend du rendu, des données et du déploiement.

Avant la mise en ligne, contrôlez le manifest et les icônes, HTTPS, la portée du service worker, le fallback hors ligne, la version des caches, l’invite de mise à jour, les limites de stockage, la confidentialité et l’installation sur de vrais appareils. React ou Next.js fournit la structure ; le manifest, le service worker et les décisions produit construisent l’expérience Progressive Web App.

Découvrez davantage de conseils pratiques dans les archives développement, ou consultez les technologies utilisées par TERNYXA pour concevoir et développer des systèmes de production.