tutoriales.com

Gestionando la Duración del Caché en PWAs: Estrategias de Expiración con Workbox

Este tutorial te guiará a través de la implementación de estrategias de expiración de caché en Progressive Web Apps (PWAs) utilizando Workbox. Aprenderás a configurar reglas para la duración y el tamaño del caché, asegurando que tus usuarios siempre tengan contenido actualizado y un rendimiento óptimo.

Intermedio15 min de lectura7 views
Reportar error

Las Progressive Web Apps (PWAs) han revolucionado la forma en que pensamos sobre las aplicaciones web, ofreciendo experiencias de usuario rápidas, fiables y atractivas, incluso sin conexión. Uno de los pilares fundamentales para lograr esto es el Service Worker y la gestión eficiente del caché. Sin una estrategia de caché adecuada, una PWA puede terminar sirviendo contenido desactualizado o, peor aún, consumiendo demasiado espacio de almacenamiento en el dispositivo del usuario.

Aquí es donde Workbox entra en juego. Workbox es una biblioteca de módulos JavaScript que facilita la escritura, gestión y depuración de Service Workers. Ofrece una abstracción poderosa sobre las APIs del Service Worker, simplificando tareas complejas como el cacheo de recursos y la gestión de estrategias de red.

En este tutorial, nos centraremos en cómo gestionar la duración del caché utilizando las capacidades de expiración que Workbox nos proporciona. Esto es crucial para mantener un equilibrio entre el rendimiento offline y la frescura del contenido.

🚀 ¿Por qué es Crucial la Expiración del Caché?

Imagina que tienes una PWA de noticias. Si cacheas las noticias indefinidamente, tus usuarios verán información obsoleta. Por otro lado, si no cacheas nada, perderán la experiencia offline. La expiración del caché nos permite definir cuándo un recurso en caché debe considerarse "viejo" y ser reemplazado o eliminado.

Beneficios Clave:

  • Frescura del Contenido: Asegura que los usuarios vean la versión más reciente de tus activos.
  • Optimización del Almacenamiento: Evita que el caché crezca indefinidamente, consumiendo espacio valioso en el dispositivo del usuario.
  • Control Fino: Permite aplicar diferentes políticas a distintos tipos de recursos (imágenes, CSS, JS, APIs).
  • Rendimiento Equilibrado: Mantiene la velocidad de carga para activos estáticos mientras se asegura la actualización de datos dinámicos.
💡 Consejo: Una buena estrategia de expiración es fundamental para la salud a largo plazo de tu PWA y la satisfacción del usuario.

🛠️ Configuración Inicial: Preparando Workbox

Antes de sumergirnos en las estrategias de expiración, necesitamos una configuración básica de Workbox en nuestro proyecto. Asumiremos que ya tienes una PWA o una aplicación web que quieres convertir en PWA.

1. Instalar Workbox

Si estás usando un bundler como Webpack, puedes instalar Workbox a través de npm o yarn.

npm install workbox-webpack-plugin --save-dev
# o
yarn add workbox-webpack-plugin --dev

Si prefieres usar Workbox directamente en tu Service Worker sin un bundler (por ejemplo, vía CDN o módulos ES), puedes omitir esto y cargar Workbox en tu sw.js.

2. Registrar el Service Worker

Tu index.html (o el archivo principal de tu aplicación) debe registrar el Service Worker.

// public/index.js (o similar)

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js')
      .then(registration => {
        console.log('Service Worker registrado con éxito:', registration.scope);
      })
      .catch(error => {
        console.error('Fallo en el registro del Service Worker:', error);
      });
  });
}

3. Crear el Archivo del Service Worker (sw.js)

Este es el corazón de nuestro control de caché. Aquí importaremos Workbox y definiremos nuestras reglas.

// public/sw.js

importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js');

if (workbox) {
  console.log('Workbox está cargado 🎉');

  // Precaching de recursos estáticos
  // Aquí, Workbox precacheará los archivos listados en el manifest generado por workbox-webpack-plugin
  workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);

} else {
  console.log('Workbox no ha podido cargarse 😬');
}
📌 Nota: Si usas `workbox-webpack-plugin`, `self.__WB_MANIFEST` será inyectado automáticamente con una lista de URLs para precachear. Si no, necesitarás generar tu propia lista o usar `workbox-build`.

⏳ Estrategias de Expiración de Caché con Workbox

Workbox ofrece el módulo workbox-expiration para gestionar la expiración del caché. Este módulo se integra perfectamente con las estrategias de cacheo de Workbox (como StaleWhileRevalidate, CacheFirst, NetworkFirst, etc.).

Las dos opciones principales para la expiración son:

  1. maxEntries: Limita el número de entradas (respuestas) en el caché.
  2. maxAgeSeconds: Define la duración máxima en segundos que una entrada puede permanecer en el caché.

Veamos cómo aplicarlas.

1. Expiración Basada en el Tiempo (maxAgeSeconds) ⏰

Esta estrategia es ideal para recursos cuyo contenido cambia con cierta frecuencia y no quieres que los usuarios vean versiones muy antiguas. Por ejemplo, imágenes de perfil, datos de API, o CSS/JS que no están precacheados.

// public/sw.js

importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js');

if (workbox) {
  // ... (precaching si aplica)

  // Ejemplo 1: Cachear imágenes con una duración de 30 días
  workbox.routing.registerRoute(
    /.*\.(?:png|jpg|jpeg|svg|gif)$/,
    new workbox.strategies.CacheFirst({
      cacheName: 'images-cache',
      plugins: [
        new workbox.expiration.Plugin({
          maxAgeSeconds: 30 * 24 * 60 * 60, // 30 días
        })
      ]
    })
  );

  // Ejemplo 2: Cachear respuestas de API con una duración de 1 hora
  workbox.routing.registerRoute(
    /^https:\/\/api\.example\.com\/data/,
    new workbox.strategies.NetworkFirst({
      cacheName: 'api-data-cache',
      plugins: [
        new workbox.expiration.Plugin({
          maxAgeSeconds: 60 * 60, // 1 hora
          purgeOnQuotaError: true, // Borrar automáticamente si el navegador se queda sin espacio
        })
      ]
    })
  );

} else {
  console.log('Workbox no ha podido cargarse 😬');
}
🔥 Importante: La limpieza de caché (cuando `maxAgeSeconds` se cumple) ocurre la próxima vez que el Service Worker se inicie y maneje un evento `fetch` o `activate`. No es instantánea.

2. Expiración Basada en el Número de Entradas (maxEntries) 📊

Esta estrategia es útil para evitar que el caché crezca indefinidamente con un gran número de recursos. Es común para el cacheo de avatares, miniaturas o páginas visitadas recientemente, donde solo queremos mantener un número limitado de los más recientes.

// public/sw.js

importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js');

if (workbox) {
  // ... (precaching si aplica)

  // Ejemplo 3: Cachear hasta 50 páginas HTML visitadas recientemente
  workbox.routing.registerRoute(
    /.*\.html$/,
    new workbox.strategies.NetworkFirst({
      cacheName: 'html-pages-cache',
      plugins: [
        new workbox.expiration.Plugin({
          maxEntries: 50, // Mantener un máximo de 50 páginas HTML
        })
      ]
    })
  );

  // Ejemplo 4: Combinar maxEntries y maxAgeSeconds para assets genéricos
  workbox.routing.registerRoute(
    /.*\.(?:js|css)$/,
    new workbox.strategies.StaleWhileRevalidate({
      cacheName: 'assets-cache',
      plugins: [
        new workbox.expiration.Plugin({
          maxEntries: 60, // Máximo 60 archivos JS/CSS
          maxAgeSeconds: 7 * 24 * 60 * 60, // Válido por 7 días
        })
      ]
    })
  );

} else {
  console.log('Workbox no ha podido cargarse 😬');
}
⚠️ Advertencia: Ten cuidado al usar `maxEntries` sin `maxAgeSeconds` para recursos que deben actualizarse con frecuencia. Podrías terminar sirviendo versiones antiguas si el número de entradas no se supera lo suficiente para activar la limpieza.

🧠 Estrategias de Cacheo y Expiración Combinadas

La verdadera potencia de Workbox se revela al combinar estrategias de cacheo con plugins de expiración. Cada estrategia (CacheFirst, NetworkFirst, StaleWhileRevalidate, NetworkOnly, CacheOnly) tiene un comportamiento diferente en cuanto a cómo interactúa con la red y el caché. Al añadir la expiración, refinamos ese comportamiento.

Tabla de Combinaciones Comunes:

Estrategia de WorkboxmaxAgeSecondsmaxEntriesCaso de Uso Típico
------------
CacheFirst✅ Sí✅ SíImágenes estáticas, fuentes (siempre servir del caché si está disponible y no expirado)
NetworkFirst✅ Sí✅ SíAPIs dinámicas, HTML (intentar red primero, caer a caché si falla, pero el caché puede expirar)
------------
StaleWhileRevalidate✅ Sí✅ SíCSS, JS, recursos que pueden ser "ligeramente" antiguos, pero queremos actualizar en segundo plano
CacheOnly❌ N/A (solo precaching)❌ N/ARecursos precacheados que no cambian (no necesitan expiración explícita)
------------
NetworkOnly❌ N/A❌ N/ASolicitudes que siempre deben ir a la red (no se cachean)

Diagrama de Flujo: CacheFirst con maxAgeSeconds

Inicio ¿Recurso en caché? ¿No expirado? Servir del caché No No Ir a la red Cachear y servir de la red

En el diagrama anterior, la expiración actúa como una puerta adicional antes de servir desde el caché, asegurando que solo se sirvan recursos frescos.

Diagrama de Flujo: StaleWhileRevalidate con maxEntries

INICIO ¿Recurso en caché? NO Servir del caché (En segundo plano) Ir a la red Actualizar caché y limpiar (maxEntries excedido) Ir a la red Cachear y servir FIN

Aquí, el maxEntries se aplica después de que una nueva respuesta se guarda en caché, manteniendo el tamaño del caché bajo control.


📈 Uso de purgeOnQuotaError

Cuando el navegador se queda sin espacio de almacenamiento para el Service Worker (cuota de almacenamiento), Workbox puede intentar purgar automáticamente los cachés marcados con purgeOnQuotaError: true. Esta es una medida de contingencia útil para evitar que tu PWA sea desinstalada por el navegador debido al exceso de uso de almacenamiento.

// public/sw.js

// ... (otras rutas)

  workbox.routing.registerRoute(
    /.*\.(?:webp|avif)$/,
    new workbox.strategies.CacheFirst({
      cacheName: 'high-res-images-cache',
      plugins: [
        new workbox.expiration.Plugin({
          maxAgeSeconds: 60 * 60 * 24 * 7, // 7 días
          maxEntries: 100,
          purgeOnQuotaError: true, // Activar purga si hay error de cuota
        })
      ]
    })
  );
⚠️ Advertencia: No confíes únicamente en `purgeOnQuotaError`. Es una última opción. Es mejor diseñar tus estrategias de caché para que no alcancen la cuota en primer lugar.

✨ Buenas Prácticas y Consideraciones Avanzadas

Al implementar la expiración de caché, ten en cuenta las siguientes buenas prácticas:

  1. Audita tu Caché: Utiliza las Developer Tools del navegador (pestaña "Application" -> "Cache Storage" y "IndexedDB") para monitorizar el tamaño y contenido de tus cachés.

  2. Diferencia Cachés: Usa cacheName distintos para diferentes tipos de recursos. Esto te da un control más granular sobre las políticas de expiración y facilita la depuración.

  3. Versiona tus URLs: Para recursos estáticos (CSS, JS, imágenes críticas) que cambian con cada despliegue, considera versionar sus URLs (ej. app.12345.js). Esto asegura que el Service Worker precachee la nueva versión y el navegador no sirva una versión antigua desde el caché HTTP o el caché del Service Worker si no has configurado la expiración.

  4. Usa backgroundSync para Revalidación: Para datos críticos de API, considera usar la API de Sincronización en Segundo Plano de Workbox si necesitas revalidar datos específicos incluso cuando el usuario está offline o ha cerrado la aplicación. Esto va más allá de la expiración de caché, pero es un complemento útil.

  5. Prueba exhaustivamente: Simula diferentes escenarios de red (offline, baja conectividad) y ciclos de vida del Service Worker (actualización, reinstalación) para asegurarte de que tus estrategias de expiración funcionan como esperas.

¿Cómo depurar problemas de caché?

Usa la pestaña Application en las Developer Tools de Chrome/Firefox:

  • Service Workers: Desregistra, actualiza y detén tu Service Worker.
  • Cache Storage: Revisa el contenido de tus cachés, elimina entradas o borra cachés completos.
  • Clear Storage: Borra todos los datos del sitio, incluyendo Service Workers y cachés, para un inicio limpio.

Además, puedes añadir console.log en tu sw.js para ver el flujo de ejecución y los mensajes de Workbox.

Ejemplo de Estrategia Completa para una PWA Típica:

// public/sw.js - Estrategia de cacheo completa

importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js');

if (workbox) {
  console.log('Workbox está cargado y listo para cachear 🎉');

  // 1. Precaching de activos generados por el build (HTML, CSS, JS versionados)
  workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);

  // 2. Cacheo de fuentes de Google Fonts (CacheFirst con expiración larga)
  workbox.routing.registerRoute(
    /^https:\/\/fonts\.(?:googleapis|gstatic)\.com\//,
    new workbox.strategies.CacheFirst({
      cacheName: 'google-fonts',
      plugins: [
        new workbox.expiration.Plugin({
          maxEntries: 30,
          maxAgeSeconds: 365 * 24 * 60 * 60, // 1 año
          purgeOnQuotaError: true,
        }),
      ],
    })
  );

  // 3. Cacheo de imágenes (CacheFirst con expiración moderada)
  workbox.routing.registerRoute(
    /.*\.(?:png|gif|jpg|jpeg|svg|webp)$/,
    new workbox.strategies.CacheFirst({
      cacheName: 'images-cache',
      plugins: [
        new workbox.expiration.Plugin({
          maxEntries: 60,
          maxAgeSeconds: 30 * 24 * 60 * 60, // 30 días
          purgeOnQuotaError: true,
        }),
      ],
    })
  );

  // 4. Cacheo de APIs (NetworkFirst con expiración corta, para datos dinámicos)
  workbox.routing.registerRoute(
    /^https:\/\/api\.tudominio\.com\/data/,
    new workbox.strategies.NetworkFirst({
      cacheName: 'api-data',
      plugins: [
        new workbox.expiration.Plugin({
          maxEntries: 20,
          maxAgeSeconds: 60 * 30, // 30 minutos
          purgeOnQuotaError: true,
        }),
      ],
    })
  );

  // 5. Cacheo de otros activos (StaleWhileRevalidate con expiración estándar)
  workbox.routing.registerRoute(
    ({ request }) => request.destination === 'script' || request.destination === 'style',
    new workbox.strategies.StaleWhileRevalidate({
      cacheName: 'static-assets',
      plugins: [
        new workbox.expiration.Plugin({
          maxEntries: 50,
          maxAgeSeconds: 7 * 24 * 60 * 60, // 7 días
          purgeOnQuotaError: true,
        }),
      ],
    })
  );

  // 6. Cacheo de páginas HTML (NetworkFirst con expiración corta)
  workbox.routing.registerRoute(
    ({ request }) => request.mode === 'navigate',
    new workbox.strategies.NetworkFirst({
      cacheName: 'html-pages',
      plugins: [
        new workbox.expiration.Plugin({
          maxEntries: 20,
          maxAgeSeconds: 60 * 60 * 2, // 2 horas
          purgeOnQuotaError: true,
        }),
      ],
    })
  );

  // Habilitar navegación offline para páginas que no están precacheadas o fallan la red
  workbox.routing.setDefaultHandler(
    new workbox.strategies.NetworkOnly()
  );

} else {
  console.log('Workbox no ha podido cargarse 😬. Asegúrate de que la URL del CDN es correcta.');
}

Este ejemplo demuestra cómo puedes combinar diferentes estrategias y reglas de expiración para gestionar eficazmente los diversos tipos de recursos en tu PWA, optimizando tanto el rendimiento como la frescura del contenido. La clave es entender la naturaleza de cada recurso y cómo esperas que se comporte en escenarios online y offline.


✅ Conclusión

La gestión de la expiración del caché es un aspecto fundamental para construir PWAs robustas y eficientes. Workbox simplifica enormemente esta tarea, proporcionando herramientas poderosas y flexibles para definir cuándo y cómo deben purgarse los recursos en caché.

Al aplicar maxAgeSeconds y maxEntries de manera inteligente a tus diferentes tipos de recursos, puedes asegurar que tus usuarios disfruten de una experiencia rápida y actualizada, manteniendo al mismo tiempo el consumo de almacenamiento bajo control. Recuerda siempre probar tus estrategias a fondo y ajustarlas según las necesidades específicas de tu aplicación.

¡Ahora estás listo para llevar el control del caché de tu PWA al siguiente nivel! 🚀

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!