Skip to content

Étape 26 — Transformer le Pokédex en PWA

Accessible à tout le monde

Cette étape fonctionne sur Windows, macOS et Linux et ne nécessite aucun outil supplémentaire : juste du code et le navigateur. Le résultat est testable en local en 30 minutes.

Temps estimé

Environ 30 minutes : 5 min d'install, 10 min de config, 5 min de tests, 10 min pour comprendre le service worker.

Objectif

Transformer votre Pokédex (actuellement une SPA) en une PWA (Progressive Web App). Concrètement, ça veut dire :

  • Installable sur l'écran d'accueil (mobile et desktop)
  • Fonctionne hors ligne une fois visité une fois (les ressources sont en cache)
  • Icône d'app dans le launcher avec nom personnalisé
  • Chargement instantané après la première visite (cache)

Pour rappel (voir E25)

Une PWA = une SPA + un manifest (métadonnées d'installation) + un service worker (cache et offline). HTTPS obligatoire en production (localhost compte comme sécurisé).

Pré-requis

  • L'étape E25 lue (vous comprenez la différence SPA / PWA)
  • Le projet Pokédex Vuetify fonctionnel localement (npm run dev OK)
  • Une connexion internet (pour npm install)

Tâches

1. Installer le plugin Vite PWA

bash
npm install --save-dev vite-plugin-pwa

Ce plugin officiel (vite-pwa-org.netlify.app) génère automatiquement le service worker, le manifest, et un fichier de précache de tous vos assets.

2. Configurer le plugin dans vite.config.mjs

Ouvrez vite.config.mjs à la racine du projet et ajoutez le plugin :

js
// Plugins
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import Fonts from 'unplugin-fonts/vite'
import Layouts from 'vite-plugin-vue-layouts'
import { VitePWA } from 'vite-plugin-pwa'   // ← AJOUT
import Vue from '@vitejs/plugin-vue'
import VueRouter from 'unplugin-vue-router/vite'
import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'

// ...

export default defineConfig({
  base: process.env.VITE_BASE_URL || './',
  plugins: [
    VueRouter(),
    Layouts(),
    Vue({ template: { transformAssetUrls } }),
    Vuetify({
      autoImport: true,
      styles: { configFile: 'src/styles/settings.scss' },
    }),
    Components(),
    Fonts({
      google: {
        families: [{ name: 'Roboto', styles: 'wght@100;300;400;500;700;900' }],
      },
    }),
    AutoImport({
      imports: ['vue', 'vue-router'],
      eslintrc: { enabled: true },
      vueTemplate: true,
    }),

    // ↓↓↓ Configuration PWA ↓↓↓
    VitePWA({
      // 'autoUpdate' : le service worker met à jour le cache automatiquement
      // dès qu'un nouveau build est déployé (recommandé pour les débutants).
      registerType: 'autoUpdate',

      // Pour tester en dev avec npm run dev (sans build)
      devOptions: {
        enabled: true,
      },

      // Manifest : métadonnées d'installation
      // (nom de l'app, icônes, couleur de la barre de statut, etc.)
      manifest: {
        name: 'Pokédex Vuetify',
        short_name: 'Pokédex',
        description: 'Encyclopédie Pokémon construite avec Vue.js et Vuetify',
        theme_color: '#121212',
        background_color: '#121212',
        display: 'standalone',
        orientation: 'portrait',
        scope: '/',
        start_url: '/',
        icons: [
          {
            src: '/icons/pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png',
          },
          {
            src: '/icons/pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png',
          },
          {
            src: '/icons/pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'maskable',
          },
        ],
      },

      // Stratégie de cache : tout ce qui est dans dist/ est mis en cache
      // au premier chargement (= app utilisable hors ligne ensuite).
      workbox: {
        globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
        // Cache des appels à l'API Vercel (1 jour)
        runtimeCaching: [
          {
            urlPattern: /^https:\/\/2025-sfa-pokedex-api\.vercel\.app\/.*/i,
            handler: 'NetworkFirst',
            options: {
              cacheName: 'pokedex-api-cache',
              expiration: {
                maxEntries: 50,
                maxAgeSeconds: 60 * 60 * 24, // 24h
              },
            },
          },
        ],
      },
    }),
    // ↑↑↑ Fin de la configuration PWA ↑↑↑
  ],
  // ... reste de la config inchangé
})

Stratégies de cache (runtimeCaching)

  • NetworkFirst : essaie le réseau, fallback cache si hors ligne. Bon pour les API.
  • CacheFirst : sert le cache, fallback réseau. Bon pour les images, fonts.
  • StaleWhileRevalidate : sert le cache + met à jour en arrière-plan. Bon compromis.

3. Créer les icônes d'app

La PWA a besoin d'icônes 192×192 et 512×512 (minimum). Créez le dossier public/icons/ et placez-y deux PNG carrés :

public/
├── favicon.ico
└── icons/
    ├── pwa-192x192.png    ← 192 × 192 pixels
    └── pwa-512x512.png    ← 512 × 512 pixels

Générer les icônes automatiquement

Si vous avez une icône source carrée (au moins 512×512), utilisez le site maskable.app/editor pour générer toutes les tailles. Pour aller plus vite, le package @vite-pwa/assets-generator automatise tout :

bash
npm install --save-dev @vite-pwa/assets-generator
npx pwa-assets-generator --preset minimal public/source-icon.png

Pour ce cours, vous pouvez télécharger une pokéball icones8.com/icon/sjEvO87Wmh32/pokeball et la redimensionner.

4. Builder et tester

bash
npm run build
npm run preview
# → ouvrir http://localhost:4173

npm run preview est obligatoire pour tester la PWA

Le service worker ne se charge pas avec npm run dev (sauf si devOptions.enabled: true comme dans notre config). Pour un test réaliste, utilisez toujours preview qui sert la version buildée.

5. Vérifier l'installation PWA

Dans Brave / Chrome / Firefox, ouvrez DevTools → onglet Application :

  1. Manifest : vérifier le nom, les icônes, theme color
  2. Service Workers : confirmer qu'il est activated and running
  3. Cache Storage : vérifier qu'un cache workbox-precache-v2 existe avec tous les fichiers

Dans la barre d'adresse, cliquer sur l'icône Installer (à côté du favicon — disponible quand la PWA est valide). Le Pokédex s'installe comme une app desktop avec sa propre fenêtre et son icône dans le launcher.

6. Tester le hors ligne

Toujours dans DevTools, onglet Network :

  1. Cocher Offline (en haut)
  2. Rafraîchir la page (Cmd/Ctrl + R)
  3. L'app continue de fonctionner — vous voyez la dernière liste de Pokémon mise en cache, et vous pouvez naviguer entre les pages

Premières interactions hors ligne uniquement

La PWA sert le cache existant. Vous ne pouvez pas créer un nouveau Pokémon hors ligne (l'appel POST à l'API échouera). Pour ça il faudrait implémenter un système de synchronisation différée (workbox-background-sync) — sortir du scope de cette étape.

7. Auditer avec Lighthouse

Dans DevTools → onglet Lighthouse :

  1. Choisir Mode : Navigation et Catégorie : PWA
  2. Cliquer Analyser
  3. Vous devriez obtenir un score PWA de 100/100 (ou très proche)

Si certains critères sont rouges (par exemple "Manifest doesn't have a maskable icon"), corrigez selon les recommandations.

Tests à effectuer

  • L'app charge sans erreur en npm run preview
  • DevTools → Application → Service Workers : activated and running
  • DevTools → Application → Manifest : nom, icônes, theme color OK
  • L'icône Installer apparaît dans la barre d'adresse
  • Une fois installée, l'app a sa propre fenêtre (pas d'onglet navigateur)
  • Mode offline (DevTools → Network) : l'app continue de fonctionner
  • Lighthouse audit PWA : score > 90

Pièges connus

HTTPS obligatoire en production

Sur Vercel, Netlify, GitHub Pages : HTTPS automatique, tout marche. Sur un serveur custom : il faut un certificat SSL (Let's Encrypt). En localhost : exception, HTTP fonctionne.

Mise à jour du service worker

Si vous modifiez le code et rebuilder, le service worker des utilisateurs ne se met à jour qu'au prochain chargement. Avec registerType: 'autoUpdate', ça marche automatiquement mais il peut y avoir un délai d'une visite.

Pour forcer un refresh : DevTools → Application → Service Workers → "Unregister" + rafraîchir.

Apple Safari iOS : restrictions

Safari sur iOS supporte les PWA mais avec des limitations :

  • Pas d'installation automatique via icône dans la barre — il faut Partager → Sur l'écran d'accueil
  • Pas de notifications push (sauf iOS 16.4+)
  • Cache limité à 50 Mo (vs ~1 Go sur Chrome Android)

Pour un vrai effet "app native" sur iPhone, utilisez plutôt Capacitor (étapes E27 à E29).

Commit

bash
git add -A
git commit -m "feat(pwa): transformer le Pokédex en PWA installable"

Récapitulatif visuel

Avant cette étape : votre Pokédex était une SPA classique consultable uniquement via navigateur, sans installation possible, ne marche pas hors ligne.

Après cette étape : votre Pokédex est installable comme une app sur mobile et desktop, fonctionne hors ligne, et se met à jour automatiquement.

Suite

Vous avez maintenant une PWA fonctionnelle. Vous pouvez :

  • Vous arrêter ici si votre objectif est uniquement "web installable + hors ligne"
  • Continuer vers le mobile natif avec E27 (Capacitor + Android) pour publier sur le Play Store et obtenir des perfs vraiment natives

PWA vs Capacitor — quel choix ?

CritèrePWACapacitor
Effort30 min1 à 3 heures
InstallationVia navigateurVia App Store / Play Store
Compte développeurGratuit99 USD/an Apple, 25 USD une fois Google
API natives (caméra, contacts, push)LimitéesComplètes
Visibilité (stores)NonOui
RecommandationApp interne, MVP rapideApp publique, monétisable

Documentation pour les cours de développement web