Service Workers: Cómo Hacer que tu Web Funcione Sin Conexión

Service Workers: Cómo Hacer que tu Web Funcione Sin Conexión

Los Service Workers son uno de los avances más importantes en web performance de la última década. Con una implementación de 1-2 horas, los repeat visitors de Quintaesencia cargan la página en 150ms en lugar de 6 segundos — 41x más rápido — y el sitio funciona sin conexión a internet.

¿Qué es un Service Worker?

Un Service Worker es un script JavaScript que corre en segundo plano, separado de la página web. Actúa como un proxy entre el navegador y el servidor: puede interceptar solicitudes de red, servir contenido desde caché local, y permitir que la página funcione sin conexión.

La analogía más clara: imagina un almacén con un gerente inteligente. Cada vez que la página pide un recurso (imagen, CSS, JS), el gerente:

  1. Verifica si el recurso ya está en el almacén local (caché)
  2. Si está: lo entrega inmediatamente, sin ir al servidor
  3. Si no está: lo descarga del servidor y lo guarda en el almacén para la próxima vez
  4. Si el servidor no responde (sin internet): sirve lo que tiene en el almacén

El resultado: carga instantánea en visitas siguientes y funcionamiento offline.

El problema que resuelven

Quintaesencia enfrenta un desafío típico de webs con muchos assets: los primeros visitantes cargan lentamente (tienen que descargar todo del servidor), pero los visitantes frecuentes deberían cargar instantáneamente.

Tipo de visitante Sin Service Worker Con Service Worker
Primera visita Descarga ~1 MB, tarda 6+ segundos Igual — primer contacto siempre es lento
Repeat visitor Vuelve a descargar todo, 6+ segundos Sirve desde caché en ~150ms
Sin internet Error 404, página vacía Muestra versión en caché
Impacto Experiencia pobre para usuarios frecuentes 41x más rápido, funciona offline

Implementación: el archivo sw.js

Un Service Worker es un archivo JavaScript en la raíz del sitio. Aquí está la implementación completa con la estrategia Cache First:

// /sw.js — en la raiz del sitio
const CACHE_NAME = "quintaesencia-v1";

const ASSETS_TO_CACHE = [
    "/",
    "/index.html",
    "/css/style.css",
    "/js/main.js",
    "/images/portada.webp",
    "/images/mapamundi.webp",
    "/audio/Rey-Salvaje.ogg",
    // ... mas assets criticos
];

// INSTALAR: se ejecuta la primera vez que el usuario visita
self.addEventListener("install", event => {
    event.waitUntil(
        caches.open(CACHE_NAME).then(cache => {
            return cache.addAll(ASSETS_TO_CACHE);
        })
    );
    self.skipWaiting(); // Activar inmediatamente
});

// FETCH: intercepta cada solicitud de recurso
self.addEventListener("fetch", event => {
    event.respondWith(
        caches.match(event.request).then(response => {
            // 1. Si esta en cache, servir inmediatamente
            if (response) return response;

            // 2. Si no esta, obtener del servidor
            return fetch(event.request).then(response => {
                // 3. Guardar en cache para proximas veces
                if (response && response.status === 200) {
                    caches.open(CACHE_NAME).then(cache => {
                        cache.put(event.request, response.clone());
                    });
                }
                return response;
            }).catch(() => {
                // 4. Sin internet: servir desde cache si existe
                return caches.match(event.request);
            });
        })
    );
});

// ACTIVAR: limpiar versiones antiguas de cache
self.addEventListener("activate", event => {
    event.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all(
                cacheNames.map(cacheName => {
                    if (cacheName !== CACHE_NAME) {
                        return caches.delete(cacheName);
                    }
                })
            );
        })
    );
    self.clients.claim();
});

Registrar el Service Worker

En tu HTML, antes del cierre de </body>:

<script>
if ("serviceWorker" in navigator) {
    navigator.serviceWorker.register("/sw.js")
        .then(reg => console.log("Service Worker registrado:", reg))
        .catch(err => console.log("Error:", err));
}
</script>

Eso es todo. En la primera visita, el Service Worker se instala y cachea los assets críticos. En la segunda visita, esos assets se sirven desde caché en milisegundos.

Estrategias de caching

Hay tres estrategias principales. La elección depende del tipo de contenido:

  • Cache First: Mira en caché primero, luego en servidor. Más rápido, pero el contenido puede estar desactualizado. Ideal para imágenes, CSS, JS estáticos.
  • Network First: Intenta el servidor primero, luego caché. Más actualizado, pero más lento. Ideal para datos dinámicos y APIs.
  • Stale While Revalidate: Sirve desde caché inmediatamente y en background actualiza desde el servidor. Lo mejor de ambos mundos para la mayoría de casos.

La estrategia Stale While Revalidate en código:

self.addEventListener("fetch", event => {
    event.respondWith(
        caches.open(CACHE_NAME).then(cache => {
            return cache.match(event.request).then(cachedResponse => {
                // Actualizar en background
                const fetchPromise = fetch(event.request).then(response => {
                    cache.put(event.request, response.clone());
                    return response;
                });
                // Retornar cache inmediatamente, o el fetch si no hay cache
                return cachedResponse || fetchPromise;
            });
        })
    );
});
// Resultado: el usuario ve contenido al instante
// y recibe actualizaciones en la siguiente visita

Code Splitting: cargar solo lo necesario

Un complemento perfecto al Service Worker es el Code Splitting: en lugar de cargar un archivo JS gigante, cargarlo en módulos pequeños bajo demanda:

// Sin Code Splitting — todo carga al abrir la pagina
import orbeAudioMaster from "./orbe-audio-master.js";   // 38 KB
import orbeResonancia from "./orbe-resonancia-mini.js"; // 82 KB
import orbeFullscreen from "./orbe-fullscreen.js";      // 45 KB
// Total: 165 KB en la carga inicial

// Con Code Splitting — cada modulo carga cuando se necesita
const orbeAudioMaster = import("./orbe-audio-master.js");  // Al abrir home
const orbeResonancia = import("./orbe-resonancia-mini.js"); // Al abrir el player
const orbeFullscreen = import("./orbe-fullscreen.js");     // Al hacer click en fullscreen

El Service Worker cachea cada módulo por separado. Si un usuario visita solo la página de inicio, no descarga los módulos del reproductor. Impacto: primera visita 165 KB → 60 KB. Segunda visita: 150ms.

Resultados reales en Quintaesencia

Métrica Sin SW Con SW Mejora
FCP primera visita 6.148 ms 6.100 ms Sin cambio
FCP repeat visitor 6.148 ms 150 ms 41x más rápido
Datos descargados (visita 2) 655 KB ~10 KB -98.5%
Funcionamiento offline Error 404 Versión en caché Funciona sin internet

Consideraciones importantes

  • Versioning crítico: Cambiar CACHE_NAME (de v1 a v2) fuerza a todos los usuarios a descargar de nuevo. Hazlo cuando actualices assets importantes.
  • No cachees todo: Especifica qué assets cachear. Cachear archivos dinámicos o muy grandes es contraproducente.
  • Depuración: El navegador puede servir desde caché aunque hayas actualizado el archivo. En DevTools → Application → Service Workers, usa «Update on Reload» durante desarrollo.
  • Límite de caché: Los navegadores tienen límites (~50 MB). Si creces mucho, el Service Worker puede ser rechazado.
  • HTTPS obligatorio: Los Service Workers solo funcionan en HTTPS (o localhost). HTTP se rechaza por seguridad.

Depurar en Chrome DevTools

Para inspeccionar el Service Worker y el caché:

  1. Abre DevTools (F12)
  2. Ve a la pestaña Application
  3. En el panel izquierdo: Service Workers — muestra el estado, permite forzar actualización
  4. En el panel izquierdo: Cache Storage — muestra todos los archivos cacheados y su tamaño
  5. Para simular offline: en la pestaña Network, selecciona «Offline» en el dropdown de throttling

Si el Service Worker no se actualiza, haz click en «Update» en la sección Service Workers de DevTools, o activa «Update on Reload» durante el desarrollo.

Los Service Workers son una de las mejoras de rendimiento con mejor ratio esfuerzo/impacto que existen. Si tu web recibe visitantes frecuentes, implementarlos debería ser una prioridad. La inversión es de pocas horas; el beneficio es permanente.

Compartir:
|
← Anterior
Cómo Crear un Sistema de Audio Global para una Web de Videojuegos
Siguiente →
Optimización Web Profesional: De 6 Segundos a 0.6 Segundos
Inicio Blog Service Workers: Cómo Hacer que tu Web Funcione Sin Conexión
← Volver al Blog