Trabajadores de servicios: el proxy de red programable
Convierta su sitio web en una aplicación compatible sin conexión. Una guía técnica sobre el ciclo de vida del Service Worker, las estrategias de almacenamiento en caché (Workbox) y la sincronización en segundo plano.
La mayor diferencia entre una aplicación nativa y una aplicación web es la Resiliencia. Si abres Instagram en modo avión, verás fotos almacenadas en caché. Si abres un sitio web estándar en modo avión, verás el dinosaurio.
Esto es inaceptable para el software moderno. Los trabajadores de servicios son la solución. Son un script de trabajo de JavaScript que se ejecuta en segundo plano, separado de una página web. Actúan como un Proxy de red del lado del cliente.
En Maison Code Paris, creamos aplicaciones web progresivas (PWA) que pasan la “prueba del metro”: ¿puede el usuario navegar por el catálogo mientras está bajo tierra? Si la respuesta es No, la aplicación no funciona.
Por qué Maison Code habla de esto
En Maison Code Paris, actuamos como la conciencia arquitectónica de nuestros clientes. A menudo heredamos stacks “modernos” construidos sin una comprensión fundamental de la escala.
Discutimos este tema porque representa un punto de inflexión crítico en la madurez de la ingeniería. Implementarlo correctamente diferencia un MVP frágil de una plataforma resistente de nivel empresarial.
Por qué Maison Code exige estar fuera de línea primero
Una pantalla blanca es un cliente perdido. Tratamos “Sin conexión” como una característica, no como un estado de error. Nuestro estándar PWA garantiza:
- Resiliencia: el shell de la aplicación se carga instantáneamente, incluso en 2G.
- Compromiso: los usuarios pueden agregar contenido al carrito sin conexión (Background Sync lo envía más tarde).
- Retención: Las notificaciones automáticas generan una tasa de retorno 3 veces mayor que el correo electrónico para nuestros clientes que utilizan dispositivos móviles. No construimos “sitios web”; construimos “Aplicaciones web instalables”.
El modelo mental: el hombre del medio
Normalmente: Navegador -> Red -> Servidor.
Con SW: Navegador -> Trabajador de servicio.
El trabajador del servicio decide entonces:
- “Tengo esto en caché. Devolver caché”. (latencia de 0 ms).
- “Necesito ir a la Red”. (Latencia estándar).
- “Vaya a Red, pero si falla, devuelva Caché”. (Resistencia).
Esta lógica es programable. Tú controlas cada byte.
El ciclo de vida (la parte difícil)
Los trabajadores de servicios son muy difíciles de depurar porque viven independientemente de la pestaña.
- Instalar: el navegador descarga
sw.js. Descarga activos críticos (Precache). - Activar: El SW se inicia. Limpia cachés antiguos. Lo más importante es que todavía no controla las pestañas abiertas.
- Claim: Debes llamar a
clients.claim()para tomar el control de las pestañas abiertas inmediatamente. - Recuperar: el software crea un detector de eventos “recuperar”.
El problema de la “nueva versión”
Implementas v2. El usuario visita el sitio.
El navegador ve sw.js cambiado. Instala v2 en segundo plano.
¡Pero v1 todavía está ejecutando la página!
v2 entra en estado “En espera”.
Solo se activará cuando todas las pestañas estén cerradas.
Para solucionar este problema, implementamos un mensaje de “Actualización disponible” en la interfaz de usuario.
// En tu aplicación React
if (registro.esperando) {
showToast("Actualización disponible", () => {
registro.esperando.postMessage({ tipo: 'SKIP_WAITING' });
ventana.ubicación.recargar();
});
}
Estrategias de almacenamiento en caché (usando Workbox)
Escribir lógica de almacenamiento en caché sin formato es propenso a errores. Usamos Google Workbox.
1. Precaché (el Shell de la aplicación)
Descargamos el esqueleto HTML, el logotipo y el paquete JS principal durante la instalación. Estos archivos están “fijados” en el caché. Están garantizados que estarán allí.
importar {precacheAndRoute} desde 'workbox-precaching';
// __WB_MANIFEST es inyectado por la herramienta de compilación (Webpack/Vite)
precacheAndRoute(self.__WB_MANIFEST);
2. Estancado mientras se revalida (contenido dinámico)
Para las llamadas API (/api/products), queremos velocidad pero también frescura.
- Paso 1: Devuelve el JSON almacenado en caché inmediatamente. (La aplicación se procesa en 50 ms).
- Paso 2: Obtenga JSON nuevo de la red.
- Paso 3: Actualizar caché.
- Paso 4: Transmitir actualización a la interfaz de usuario (“Nuevos precios disponibles”).
importar {registerRoute} desde 'enrutamiento de caja de trabajo';
importar { StaleWhileRevalidate } desde 'estrategias de caja de trabajo';
registrarRuta(
({ url }) => url.pathname.startsWith('/api/'),
nuevo StaleWhileRevalidate({
nombre de caché: 'api-cache',
complementos: [
new ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 3600 }), // Mantener durante 1 hora
],
})
);
3. Caché primero (activos inmutables)
Para las imágenes de productos (que están alojadas en CDN con URL versionadas), utilizamos Cache First. Si está en caché, devuélvelo. Nunca accedas a la red.
Almacenamiento sin conexión: IndexedDB
LocalStorage es sincrónico. Bloquea el hilo principal.
Los trabajadores de servicios son asincrónicos. No pueden acceder a LocalStorage.
Debes utilizar IndexedDB.
Es una base de datos NoSQL dentro del navegador.
Lo usamos para almacenar “Acciones Pendientes”.
Escenario: el usuario hace clic en “Agregar al carrito” sin conexión.
- La aplicación detecta sin conexión.
- La aplicación escribe una acción en IndexedDB:
{ tipo: 'ADD_TO_CART', id: 123 }. - La aplicación actualiza la interfaz de usuario de manera optimista (la insignia muestra “1”).
- Service Worker registra un evento Sincronización en segundo plano.
Sincronización en segundo plano
Esta es la característica principal. Cuando se restablece la conexión (¡incluso si el usuario cerró la aplicación!), el sistema operativo activa al trabajador del servicio.
// sw.js
self.addEventListener('sincronización', (evento) => {
if (event.tag === 'carrito de sincronización') {
event.waitUntil(syncCart());
}
});
función asíncrona syncCart() {
const db = esperar openDB('mi-tienda');
acción constante = esperar db.get('pendiente', 'carrito');
await fetch('/api/cart', {método: 'POST', cuerpo: JSON.stringify(action) });
}
Errores comunes
- Almacenamiento en caché del propio trabajador del servicio:
Asegúrese de que su servidor envíe
Cache-Control: no-cacheparasw.js. Si el navegador almacena en caché el archivo SW durante 24 horas, no podrá implementar actualizaciones durante 24 horas. - Respuestas opacas (CORS):
Si recupera una imagen de
cdn.shopify.comsin encabezados CORS, el SW ve una “Respuesta opaca”. No puede verificar el código de estado. Podría almacenar en caché una página de error 404 como una imagen. Configure siempre CORS en sus depósitos. - Cuota excedida: Los navegadores limitan el almacenamiento (normalmente entre el 10 y el 20 % del espacio en disco). Implemente siempre políticas de desalojo menos utilizadas recientemente (LRU) (utilizando el complemento de caducidad de Workbox).
10. Sincronización periódica en segundo plano
La sincronización estándar funciona cuando se restablece la conexión.
Sincronización periódica permite que la aplicación se active en segundo plano (por ejemplo, una vez cada 24 horas) para buscar contenido nuevo.
Imagine una aplicación de noticias. Quiere que el usuario tenga los titulares de la mañana antes de abrir la aplicación.
registration.periodicSync.register('get-headlines', {minInterval: 24 * 60 * 60 * 1000 });.
Nota: Esto generalmente requiere que la PWA esté instalada en la pantalla de inicio.
11. Consejos de depuración (Chrome DevTools)
La pestaña “Aplicación” en Chrome es tu mejor amiga.
- Actualizar al recargar: marque esta casilla durante el desarrollo. Obliga al navegador a omitir la caché de SW para el archivo SW.
- Dar de baja: Opción nuclear. Si las cosas van raras, cancele el registro del software para empezar de nuevo.
- Almacenamiento en caché: vea exactamente qué blobs JSON se guardan. Si
api-cacheestá vacío, su comparador Regex es incorrecto.
13. Cifrado de carga útil de notificaciones push
Web Push está cifrado (AES-128-GCM).
El navegador genera un par de claves pública/privada.
El servidor (VAPID) debe cifrar la carga útil con la clave pública del navegador.
Si envía JSON simple, el navegador lo rechaza.
El trabajador del servicio recibe el evento “push”, lo descifra (gestionado por el sistema operativo del navegador) y muestra la notificación.
self.registration.showNotification(data.title, {body: data.body, icon: '/icon.png' }).
Espere hasta que la promesa se resuelva (event.waitUntil) para asegurarse de que la notificación aparezca incluso si el SW se cierra inmediatamente.
14. Módulos de Workbox: Utilice la plataforma
No reinventes la rueda.
workbox-precaching, workbox-routing, workbox-strategies, workbox-expiration.
También usamos workbox-window en el hilo principal para comunicarnos con el SW.
const wb = nueva caja de trabajo('/sw.js'); wb.addEventListener('instalado', ...).
Esto abstrae la compleja API navigator.serviceWorker y maneja las peculiaridades del navegador (Safari).
15. La historia de la Web sin conexión (AppCache)
Antes de Service Workers, teníamos AppCache. Se definió en 3 palabras: “AppCache es un idiota”. Fue declarativo, estricto y lo rompió todo. Si cambió 1 byte en el manifiesto, el navegador descargó TODO nuevamente. Service Workers lo reemplazó con una API imperativa. Escribes código. Tú decides qué hacer. Este cambio de la Configuración al Código es la razón por la que los Service Workers tuvieron éxito donde AppCache falló.
16. Los obstáculos del Safari (iOS)
Apple tiene una relación de amor y odio con las PWA. Apoyan a los trabajadores de servicios, pero:
- Almacenamiento: Eliminan datos si no se utilizan durante 7 días (ITP).
- Push: solo se admite en iOS 16.4+ (y el usuario DEBE agregarlo a la pantalla de inicio).
- Sincronización: La sincronización en segundo plano es extremadamente limitada. Creamos aplicaciones “progresivas”. Significado: funcionan perfectamente en Chrome. Se degradan con gracia en Safari (vuelven a Sólo red si falla el software).
17. Conclusión
Un trabajador de servicio no es sólo para “sin conexión”. Es una herramienta de rendimiento. Desacopla la hora de inicio de la aplicación de la calidad de la red. En una conexión 4G inestable, un trabajador de servicio marca la diferencia entre un rebote y una venta.
En Maison Code, tratamos la red como “infraestructura no confiable” y le brindamos resiliencia al cliente.
¿Estás cansado de cargar hilanderos?
¿Sus usuarios de dispositivos móviles sufren de malas conexiones? Contrate a nuestros arquitectos.