MAISON CODE .
/ PWA · Offline · Caching · Performance · Architecture

Addetti ai servizi: il proxy di rete programmabile

Trasforma il tuo sito web in un'applicazione con funzionalità offline. Una guida tecnica al ciclo di vita del Service Worker, alle strategie di memorizzazione nella cache (Workbox) e alla sincronizzazione in background.

AB
Alex B.
Addetti ai servizi: il proxy di rete programmabile

La differenza più grande tra un’app nativa e un’app Web è la Resilienza. Se apri Instagram in modalità aereo, vedi le foto memorizzate nella cache. Se apri un sito Web standard in modalità aereo, vedi il dinosauro.

Questo è inaccettabile per il software moderno. Gli addetti ai servizi sono la soluzione. Sono uno script di lavoro JavaScript che viene eseguito in background, separato da una pagina web. Fungono da proxy di rete lato client.

A Maison Code Paris, creiamo Progressive Web App (PWA) che superano il “Subway Test”: l’utente può sfogliare il catalogo mentre si trova sottoterra? Se la risposta è No, l’app è danneggiata.

Perché Maison Code ne parla

In Maison Code Paris, agiamo come la coscienza architettonica dei nostri clienti. Spesso ereditiamo stack “moderni” costruiti senza una comprensione fondamentale della scala.

Discutiamo di questo argomento perché rappresenta un punto di svolta critico nella maturità ingegneristica. Implementarlo correttamente differenzia un MVP fragile da una piattaforma resiliente di livello aziendale.

Perché Maison Code richiede l’offline-first

Uno schermo bianco è un cliente perso. Trattiamo “Offline” come una funzionalità, non come uno stato di errore. Il nostro standard PWA garantisce:

  • Resilienza: la shell dell’app si carica istantaneamente, anche su 2G.
  • Coinvolgimento: gli utenti possono aggiungere al carrello mentre sono offline (la sincronizzazione in background lo invia in un secondo momento).
  • Fidelizzazione: le notifiche push generano un tasso di rendimento 3 volte superiore rispetto alle e-mail per i nostri clienti mobile-first. Non realizziamo “Siti Web”; realizziamo “Web App Installabili”.

Il modello mentale: l’uomo nel mezzo

Normalmente: Browser -> Rete -> Server. Con SW: Browser -> Service Worker. Il Service Worker poi decide:

  1. “Ho questo in Cache. Return Cache.” (0ms di latenza).
  2. “Devo andare in Rete.” (Latenza standard).
  3. “Vai su Rete, ma se fallisce, restituisci Cache.” (Resilienza).

Questa logica è programmabile. Controlli ogni byte.

Il ciclo di vita (la parte difficile)

I Service Worker sono notoriamente difficili da eseguire il debug perché vivono indipendentemente dal Tab.

  1. Installa: il browser scarica sw.js. Scarica risorse critiche (Precache).
  2. Attiva: Il SW si avvia. Pulisce le vecchie cache. Fondamentalmente, non controlla ancora le schede aperte.
  3. Claim: devi chiamare clients.claim() per assumere immediatamente il controllo delle schede aperte.
  4. Fetch: il SW crea un ascoltatore di eventi fetch.

Il problema della “nuova versione”.

Distribuisci “v2”. L’utente visita il sito. Il browser vede sw.js cambiato. Installa “v2” in background. Ma v1 sta ancora eseguendo la pagina! “v2” entra nello stato “In attesa”. Si attiverà solo quando tutte le schede saranno chiuse. Per risolvere questo problema, implementiamo un avviso “Aggiornamento disponibile” nell’interfaccia utente.

// Nella tua app React
if (registrazione.in attesa) {
  showToast("Aggiornamento disponibile", () => {
    registrazione.waiting.postMessage({ tipo: 'SKIP_WAITING' });
    finestra.posizione.reload();
  });
}

Strategie di memorizzazione nella cache (utilizzando Workbox)

La scrittura della logica di memorizzazione nella cache non elaborata è soggetta a errori. Utilizziamo Google Workbox.

1. Precache (la shell dell’app)

Scarichiamo lo scheletro HTML, il logo e il bundle JS principale durante l’installazione. Questi file sono “Bloccati” nella cache. È garantito che saranno lì.

import { precacheAndRoute } da 'workbox-precaching';
// __WB_MANIFEST viene iniettato dallo strumento di compilazione (Webpack/Vite)
precacheAndRoute(self.__WB_MANIFEST);

2. Stale-While-Revalidate (Contenuto dinamico)

Per le chiamate API (/api/products), vogliamo velocità ma anche freschezza.

  • Passaggio 1: restituisci immediatamente il JSON memorizzato nella cache. (L’app viene renderizzata in 50 ms).
  • Passaggio 2: recupera il nuovo JSON dalla rete.
  • Passaggio 3: aggiorna la cache.
  • Passaggio 4: trasmissione dell’aggiornamento all’interfaccia utente (“Nuovi prezzi disponibili”).
import {registerRoute} da 'workbox-routing';
import { StaleWhileRevalidate } da 'workbox-strategies';

RegisterRoute(
  ({ url }) => url.nomepercorso.startsWith('/api/'),
  new StaleWhileRevalidate({
    cacheName: 'api-cache',
    plugin: [
      nuovo ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 3600 }), // Conserva per 1 ora
    ],
  })
);

3. Prima la cache (risorse immutabili)

Per le immagini dei prodotti (che sono ospitate su CDN con URL con versione), utilizziamo Cache First. Se è nella cache, restituiscilo. Non colpire mai la rete.

Archiviazione offline: DB indicizzato

“LocalStorage” è sincrono. Blocca il thread principale. I Service Worker sono asincroni. Non possono accedere a LocalStorage. È necessario utilizzare IndexedDB. È un database NoSQL all’interno del browser. Lo usiamo per memorizzare “Azioni in sospeso”.

Scenario: l’utente fa clic su “Aggiungi al carrello” mentre è offline.

  1. L’app rileva offline.
  2. L’app scrive l’azione su IndexedDB: { type: 'ADD_TO_CART', id: 123 }.
  3. L’app aggiorna in modo ottimistico l’interfaccia utente (il badge mostra “1”).
  4. Service Worker registra un evento di Sincronizzazione in background.

Sincronizzazione in background

Questa è la caratteristica killer. Quando la connessione ritorna (anche se l’utente ha chiuso l’app!), il sistema operativo riattiva il Service Worker.

// sw.js
self.addEventListener('sync', (evento) => {
  if (event.tag === 'sincronizzazione-carrello') {
    evento.waitUntil(syncCart());
  }
});

funzione asincrona syncCart() {
  const db = attendono openDB('mio-negozio');
  const azione = attendono db.get('in sospeso', 'carrello');
  attendono fetch('/api/cart', { metodo: 'POST', corpo: JSON.stringify(azione) });
}

Insidie ​​comuni

  1. Memorizza nella cache il Service Worker stesso: Assicurati che il tuo server invii “Cache-Control: no-cache” per “sw.js”. Se il browser memorizza nella cache il file SW per 24 ore, non è possibile distribuire gli aggiornamenti per 24 ore.
  2. Risposte opache (CORS): Se recuperi un’immagine da cdn.shopify.com senza intestazioni CORS, il SW vede una “risposta opaca”. Non è possibile verificare il codice di stato. Potrebbe memorizzare nella cache una pagina di errore 404 come immagine. Configura sempre CORS sui tuoi bucket.
  3. Quota superata: I browser limitano lo spazio di archiviazione (solitamente il 10-20% dello spazio su disco). Implementare sempre le politiche di eliminazione LRU (Least Recently Used) (utilizzando il plug-in di scadenza Workbox).

10. Sincronizzazione periodica dello sfondo

La sincronizzazione standard funziona quando la connessione ritorna. La Sincronizzazione periodica consente all’app di attivarsi in background (ad esempio, una volta ogni 24 ore) per recuperare nuovi contenuti. Immagina un’app di notizie. Vuoi che l’utente abbia i titoli del mattino prima di aprire l’app. registration.periodicSync.register('get-headlines', { minInterval: 24 * 60 * 60 * 1000 });. Nota: questo di solito richiede l’installazione della PWA nella schermata principale.

11. Suggerimenti per il debug (Chrome DevTools)

La scheda “Applicazione” in Chrome è la tua migliore amica.

  1. Aggiorna al ricaricamento: seleziona questa casella durante lo sviluppo. Costringe il browser a bypassare la cache SW per il file SW stesso.
  2. Annulla registrazione: opzione nucleare. Se le cose sono strane, annulla la registrazione del SW per ricominciare da capo.
  3. Archiviazione cache: visualizza esattamente quali BLOB JSON vengono salvati. Se “api-cache” è vuoto, il tuo matcher Regex è sbagliato.

13. Crittografia del payload delle notifiche push

Web Push è crittografato (AES-128-GCM). Il browser genera una coppia di chiavi pubblica/privata. Il server (VAPID) deve crittografare il payload con la chiave pubblica del browser. Se invii un semplice JSON, il browser lo rifiuta. Il Service Worker riceve l’evento “push”, lo decrittografa (gestito dal sistema operativo del browser) e mostra la notifica. self.registration.showNotification(data.title, { body: data.body, icon: '/icon.png' }). Attendi fino alla risoluzione della promessa (event.waitUntil) per garantire che la notifica venga visualizzata anche se il SW viene terminato immediatamente.

14. Moduli Workbox: utilizzare la piattaforma

Non reinventare la ruota. workbox-precaching, workbox-routing, workbox-strategies, workbox-expiration. Usiamo anche workbox-window nel thread principale per comunicare con il SW. const wb = new Workbox('/sw.js'); wb.addEventListener('installato', ...). Ciò elimina la complessa API navigator.serviceWorker e gestisce le peculiarità del browser (Safari).

15. La storia del Web offline (AppCache)

Prima di Service Workers, avevamo AppCache. È stato definito in 3 parole: “AppCache è un cretino”. Era dichiarativo, severo e ha rotto tutto. Se hai modificato 1 byte nel manifest, il browser ha scaricato di nuovo TUTTO. I Service Worker lo hanno sostituito con un’API Imperative. Scrivi il codice. Decidi tu cosa fare. Questo passaggio dalla configurazione al codice è il motivo per cui i Service Worker hanno avuto successo laddove AppCache ha fallito.

16. Gli ostacoli di Safari (iOS)

Apple ha un rapporto di amore-odio con le PWA. Supportano i lavoratori dei servizi, ma:

  1. Archiviazione: cancellano i dati se non utilizzati per 7 giorni (ITP).
  2. Push: supportato solo in iOS 16.4+ (e l’utente DEVE aggiungere alla schermata iniziale).
  3. Sincronizzazione: la sincronizzazione in background è estremamente limitata. Realizziamo app “Progressive”. Significato: funzionano perfettamente su Chrome. Si degradano con garbo su Safari (ricadendo su Solo rete se il SW fallisce).

17. Conclusione

Un Service Worker non è solo per “Offline”. È uno strumento di prestazione. Disaccoppia l’ora di inizio dell’applicazione dalla qualità della rete. Su una connessione 4G instabile, un addetto ai servizi fa la differenza tra un rimbalzo e una vendita.

Noi di Maison Code trattiamo la rete come un‘“infrastruttura inaffidabile” e infondiamo resilienza nel cliente.


Stanco di caricare gli spinner?

I tuoi utenti mobili soffrono di connessioni scadenti?

Rendilo prima offline. Assumi i nostri architetti.