JavaScript

Service Workers: Cache-First vs Network-First - ¿Cuál Usar y Por Qué?

Autorangel cruz
Publicado
Lectura5 min de lectura

Los Service Workers son uno de los pilares de las Progressive Web Apps (PWA), permitiendo que tus aplicaciones web funcionen offline, carguen más rápido y ofrezcan una experiencia similar a las apps nativas. Pero... ¿cómo decides qué contenido cachear y cuándo servirlo? Ahí es donde entran las estrategias de caching.

Elegir la estrategia incorrecta puede resultar en:

  • Usuarios viendo contenido desactualizado (aunque tengan internet)
  • Carga lenta innecesaria
  • Experiencia offline rota

En este artículo te voy a explicar las principales estrategias, cuándo usar cada una, y te mostraré código real que puedes implementar hoy mismo.

Las 5 estrategias fundamentales

1. Cache-First (Cache, Fallback Network)

¿Cómo funciona? El Service Worker busca primero en el caché. Si encuentra el recurso, lo sirve inmediatamente. Si no está cacheado, va a la red.

// Estrategia Cache-First
async function cacheFirst(request, cacheName) {
    // Buscar en caché primero
    const cachedResponse = await caches.match(request);
    if (cachedResponse) {
        return cachedResponse; // Rápido: sirve de caché
    }
 
    // Si no está en caché, ir a la red
    try {
        const networkResponse = await fetch(request);
        if (networkResponse && networkResponse.status === 200) {
            // Cachear para próximas visitas
            const cache = await caches.open(cacheName);
            cache.put(request, networkResponse.clone());
        }
        return networkResponse;
    } catch (error) {
        console.error('Fetch failed:', error);
        throw error;
    }
}

¿Cuándo usarla?

  • Assets estáticos (JS, CSS, fonts, imágenes)
  • Recursos con versionado (ej: app.v2.min.js)
  • Contenido que raramente cambia

Ventajas:

  • Velocidad máxima (carga instantánea de caché)
  • Funciona offline para contenido visitado

Desventajas:

  • Puede servir contenido desactualizado
  • Requiere estrategia de invalidación de caché

2. Network-First (Network, Fallback Cache)

¿Cómo funciona? Siempre intenta ir a la red primero. Solo si falla (usuario offline), recurre al caché.

// Estrategia Network-First
async function networkFirst(request, cacheName) {
    try {
        // Intentar red primero
        const networkResponse = await fetch(request);
 
        if (networkResponse && networkResponse.status === 200) {
            // Actualizar caché con contenido fresco
            const cache = await caches.open(cacheName);
            cache.put(request, networkResponse.clone());
        }
 
        return networkResponse; // Contenido fresco
    } catch (error) {
        // Si falla la red, buscar en caché
        const cachedResponse = await caches.match(request);
        if (cachedResponse) {
            return cachedResponse; // Fallback offline
        }
 
        throw error; // Sin red ni caché
    }
}

¿Cuándo usarla?

  • Páginas HTML (contenido principal)
  • APIs con datos dinámicos
  • Contenido que debe estar actualizado

Ventajas:

  • Siempre sirve contenido fresco cuando hay conexión
  • Fallback offline para páginas ya visitadas

Desventajas:

  • Más lento que cache-first (espera red primero)
  • Consume datos aunque el contenido esté cacheado

3. Stale-While-Revalidate

¿Cómo funciona? Sirve de caché inmediatamente (incluso si está desactualizado), pero actualiza en segundo plano para la próxima visita.

// Estrategia Stale-While-Revalidate
async function staleWhileRevalidate(request, cacheName) {
    const cache = await caches.open(cacheName);
 
    // Buscar en caché
    const cachedResponse = await caches.match(request);
 
    // Actualizar en segundo plano (no esperar)
    const fetchPromise = fetch(request).then((networkResponse) => {
        if (networkResponse && networkResponse.status === 200) {
            cache.put(request, networkResponse.clone());
        }
        return networkResponse;
    });
 
    // Servir caché inmediatamente si existe
    return cachedResponse || fetchPromise;
}

¿Cuándo usarla?

  • Avatares de usuario
  • Imágenes de productos
  • Contenido que puede estar "un poco desactualizado"

Ventajas:

  • Carga instantánea (usa caché)
  • Se auto-actualiza en segundo plano
  • Funciona offline

Desventajas:

  • Usuario puede ver contenido desactualizado temporalmente
  • Consume ancho de banda en cada visita (actualización background)

4. Network-Only

¿Cómo funciona? Siempre va a la red, nunca usa caché. Es como no tener Service Worker para ese recurso.

// Estrategia Network-Only
async function networkOnly(request) {
    return fetch(request); // Directo a la red
}

¿Cuándo usarla?

  • Requests POST/PUT/DELETE (no cachear mutaciones)
  • Datos extremadamente sensibles al tiempo
  • APIs de terceros sin control

5. Cache-Only

¿Cómo funciona? Solo sirve de caché, nunca va a la red. Útil para precaching durante instalación del SW.

// Estrategia Cache-Only
async function cacheOnly(request) {
    return caches.match(request);
}

¿Cuándo usarla?

  • Assets precargados durante instalación
  • Recursos offline-first

Implementación práctica: Service Worker completo

Aquí te dejo un Service Worker funcional que usa diferentes estrategias según el tipo de contenido:

const CACHE_VERSION = 'v1';
const STATIC_CACHE = `static-${CACHE_VERSION}`;
const DYNAMIC_CACHE = `dynamic-${CACHE_VERSION}`;
const IMAGE_CACHE = `images-${CACHE_VERSION}`;
 
// Precachear assets críticos
const PRECACHE_ASSETS = [
    '/',
    '/app.css',
    '/app.js',
];
 
// Instalación: precachear
self.addEventListener('install', (event) => {
    event.waitUntil(
        caches.open(STATIC_CACHE).then((cache) => {
            return cache.addAll(PRECACHE_ASSETS);
        })
    );
    self.skipWaiting();
});
 
// Activación: limpiar cachés viejos
self.addEventListener('activate', (event) => {
    event.waitUntil(
        caches.keys().then((cacheNames) => {
            return Promise.all(
                cacheNames
                    .filter((name) => name !== STATIC_CACHE
                                   && name !== DYNAMIC_CACHE
                                   && name !== IMAGE_CACHE)
                    .map((name) => caches.delete(name))
            );
        })
    );
    return self.clients.claim();
});
 
// Fetch: aplicar estrategias según tipo de recurso
self.addEventListener('fetch', (event) => {
    const { request } = event;
    const url = new URL(request.url);
 
    // Solo GET requests
    if (request.method !== 'GET') return;
 
    // Assets estáticos: Cache-First
    if (isStaticAsset(url)) {
        event.respondWith(cacheFirst(request, STATIC_CACHE));
    }
    // Imágenes: Stale-While-Revalidate
    else if (isImage(url)) {
        event.respondWith(staleWhileRevalidate(request, IMAGE_CACHE));
    }
    // Páginas HTML: Network-First
    else if (isNavigationRequest(request)) {
        event.respondWith(networkFirst(request, DYNAMIC_CACHE));
    }
});
 
// Helpers
function isStaticAsset(url) {
    return url.pathname.match(/\.(js|css|woff2?|ttf)$/);
}
 
function isImage(url) {
    return url.pathname.match(/\.(jpg|jpeg|png|gif|webp|avif|svg)$/);
}
 
function isNavigationRequest(request) {
    return request.mode === 'navigate';
}

Caso real: ¿Artículo nuevo en tu blog?

Esta fue la pregunta que inspiró este artículo: ¿qué pasa cuando publicas contenido nuevo?

Con network-first para páginas HTML:

// Usuario visita /blog/articulo-nuevo
fetch('/blog/articulo-nuevo')

// 1. SW intenta la RED primero
if (usuario_online) {
  // Descarga artículo nuevo
  // Lo cachea para futuras visitas
  // Usuario ve contenido FRESCO
} else {
  // 2. Usuario offline
  // Red falla
  // No está en caché (nunca visitó)
  // Error nativo del navegador
}

Resultado: Artículos nuevos siempre se descargan frescos. El caché solo funciona como fallback offline para contenido ya visitado.

Tabla comparativa rápida

Estrategia Velocidad Contenido Fresco Offline Mejor para
Cache-First Muy alta No JS, CSS, fonts
Network-First Media Sí* HTML, APIs
Stale-While-Revalidate Alta Parcial Imágenes, avatares
Network-Only Media No POST/PUT/DELETE
Cache-Only Muy alta No Precached assets

*Solo funciona offline para contenido previamente visitado.

Preguntas Frecuentes

¿Puedo combinar varias estrategias en un mismo Service Worker?

Sí, de hecho es la mejor práctica. Usa cache-first para assets estáticos, network-first para HTML, y stale-while-revalidate para imágenes.

¿Cómo actualizo el caché cuando cambio mi código?

Cambia el CACHE_VERSION en tu Service Worker. El evento activate limpiará automáticamente cachés viejos.

¿Qué pasa si el usuario nunca visitó una página y está offline?

Con network-first, si la página no está cacheada y no hay conexión, el navegador mostrará su error nativo de "No hay conexión".

¿Service Workers consumen mucho espacio?

No necesariamente. Puedes limitar el tamaño de caché o usar estrategias de expiración. Workbox (de Google) tiene helpers para esto.

¿Funciona en todos los navegadores?

Service Workers son soportados por Chrome, Firefox, Safari, Edge. IE11 no los soporta (pero ya está deprecado).

¿Cómo debugging un Service Worker?

Chrome DevTools → Application → Service Workers. Ahí puedes ver el SW activo, desregistrarlo, y simular offline.

Recursos adicionales

Si quieres profundizar más, te recomiendo:

Conclusión

Las estrategias de caching en Service Workers son la clave para construir aplicaciones web rápidas, resilientes y que funcionen offline. No existe una "mejor estrategia" universal - todo depende del tipo de contenido:

  • Assets estáticos → Cache-First (velocidad)
  • Contenido dinámico → Network-First (frescura)
  • Imágenes/Avatares → Stale-While-Revalidate (balance)

Con la implementación correcta, puedes ofrecer experiencias que rivalicen con apps nativas, manteniendo tu código simple y mantenible. ¿Ya implementaste Service Workers en tu proyecto? Cuéntame en los comentarios qué estrategia te funcionó mejor.

Bio
Angel Cruz

Desarrollador web full-stack enfocado en React, buenas prácticas y código abierto. Apasionado por construir productos útiles y compartir lo aprendido en el camino.