JavaScript

Service Workers: Estrategias de caché y arquitectura offline (Cache first, Network first).

Guía técnica para entender Service Workers como una capa de arquitectura offline: ciclo de vida, Cache Storage, estrategias de caché y decisiones reales entre Cache first, Network first y stale-while-revalidate.

Contenido de la guía

⚙️ Arquitectura web · Resiliencia · Rendimiento percibido

Un Service Worker no es “un script para guardar archivos en caché”. Es una capa de ejecución entre tu aplicación y la red: intercepta peticiones, define políticas de respuesta y decide si la experiencia depende del servidor, del caché o de una combinación inteligente de ambos.

📦 Cache Storage 🌐 Intercepción de requests ⚡ Menor latencia percibida 📴 Experiencia offline
Vas a dejar de pensar el caché como un truco de rendimiento y vas a empezar a diseñarlo como una política de arquitectura: qué se sirve desde red, qué se sirve desde caché y qué debe sobrevivir aunque la conexión falle.

Gran parte de las aplicaciones web se rompen de forma silenciosa cuando la red deja de comportarse como suponíamos. Pantallas vacías, assets que tardan demasiado, navegación inconsistente, contenido que desaparece al primer corte de conectividad. El problema no suele ser solo “lentitud”. El problema es no haber definido una estrategia explícita para responder cuando la red no llega, llega tarde o llega mal.

1. Qué es realmente un Service Worker

Un Service Worker es un script que vive fuera del hilo principal de la página y que el navegador puede ejecutar como intermediario entre tu aplicación y la red. Su trabajo no es pintar componentes ni manipular el DOM, sino interceptar eventos del ciclo de vida web: instalación, activación, peticiones de red, sincronización y notificaciones.

La idea importante no es “corre en segundo plano”, sino esta: puede decidir cómo responder a una petición antes de que esa petición llegue a la aplicación. Y en cuanto tienes ese poder, dejas de depender ciegamente de la red en cada navegación.

Piensa en él así: el navegador ya no pregunta siempre “¿qué devuelve el servidor?”, sino “¿qué política definió esta aplicación para responder a esta URL?”.

2. El ciclo de vida: instalar, activar y empezar a controlar

La arquitectura de Service Workers se entiende mucho mejor cuando separas sus tres momentos principales: install, activate y fetch. Cada uno responde a una responsabilidad distinta.

Install

  • Es el momento de precachear recursos críticos.
  • Sirve para dejar preparada una base offline mínima.
  • Si falla, el worker no queda instalado.

Activate

  • Sirve para limpiar cachés viejos.
  • Marca la transición entre versiones del worker.
  • Es donde se resuelve deuda de despliegues anteriores.

Fetch

  • Intercepta peticiones de navegación y recursos.
  • Aplica estrategias como Cache first o Network first.
  • Es donde vive la política real de respuesta.

La idea importante

  • Install prepara.
  • Activate consolida.
  • Fetch decide.
if ("serviceWorker" in navigator) {
  window.addEventListener("load", async () => {
    try {
      const registration = await navigator.serviceWorker.register("/sw.js");
      console.log("Service Worker registrado:", registration.scope);
    } catch (error) {
      console.error("Error registrando el Service Worker:", error);
    }
  });
}

Este registro es apenas la puerta de entrada. Lo importante empieza dentro de sw.js, donde defines qué recursos quieres preparar, cómo versionas tus cachés y qué política de respuesta aplicas a cada petición.

const STATIC_CACHE = "static-v1";
const APP_SHELL = [
  "/",
  "/offline/",
  "/static/css/app.css",
  "/static/js/app.js",
  "/static/img/logo.png"
];

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(STATIC_CACHE).then((cache) => {
      return cache.addAll(APP_SHELL);
    })
  );
});

3. Precache: preparar la base mínima de la experiencia

El precache no consiste en guardar “todo por si acaso”. Consiste en decidir cuál es el mínimo operativo que tu aplicación necesita para seguir siendo utilizable sin depender completamente de la red.

Esa base suele incluir el app shell: layout principal, estilos, scripts críticos, iconos y, si tienes una ruta de degradación elegante, una página /offline/. Lo importante es no confundir precache con acumulación indiscriminada. Si metes demasiado, subes coste de instalación y vuelves más frágil cada despliegue.

const ALLOWED_CACHES = [STATIC_CACHE, "dynamic-v1"];

self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches.keys().then((keys) => {
      return Promise.all(
        keys.map((key) => {
          if (!ALLOWED_CACHES.includes(key)) {
            return caches.delete(key);
          }
        })
      );
    })
  );
});

4. Activación: el caché también necesita gobernanza

Una de las formas más rápidas de degradar una PWA es tratar el caché como si fuera eterno. No lo es. Cada nueva versión del Service Worker puede introducir recursos distintos, nombres distintos o políticas distintas. Y si no limpias lo viejo, empiezas a cargar deuda técnica en el navegador del usuario.

La activación es el momento donde tu arquitectura de caché deja claro algo importante: cachear no es solo guardar; también es saber invalidar.

5. Cache first: velocidad inmediata cuando la frescura no es crítica

La estrategia Cache first pregunta primero al caché y solo va a la red si no encuentra respuesta. Es la opción natural cuando el recurso es relativamente estable y el coste de pedirlo cada vez no tiene sentido.

Cuándo tiene sentido

  • CSS, JS versionado y assets estáticos.
  • Fuentes, logos e imágenes de interfaz.
  • Recursos donde la latencia importa más que la frescura absoluta.
self.addEventListener("fetch", (event) => {
  const request = event.request;

  if (request.method !== "GET") return;

  event.respondWith(
    caches.match(request).then((cachedResponse) => {
      if (cachedResponse) {
        return cachedResponse;
      }

      return fetch(request).then((networkResponse) => {
        return caches.open("dynamic-v1").then((cache) => {
          cache.put(request, networkResponse.clone());
          return networkResponse;
        });
      });
    })
  );
});

Su ventaja es evidente: la respuesta suele ser casi instantánea y la aplicación se siente mucho más estable. Su límite también: puedes servir contenido desactualizado si aplicas esta política a recursos que deberían reflejar cambios frecuentes.

Error común: usar Cache first para HTML dinámico, dashboards o feeds que necesitan frescura constante. El resultado puede ser una aplicación “rápida”, sí, pero conceptualmente vieja.

Estás viendo solo el 60% del contenido. Hazte Premium para acceder a la guía completa.

Comunidad

Comentarios y valoraciones

No hay comentarios aún. ¡Sé el primero en opinar!