¿Qué es una PWA?
Una Progressive Web App es un sitio web que se comporta como una aplicación instalada: funciona sin conexión, carga instantáneamente en visitas recurrentes y puede añadirse a la pantalla de inicio o al escritorio. No existe una única tecnología llamada PWA; se trata de un conjunto de capacidades que se adoptan de forma incremental.
Tres ingredientes hacen que funcione:
- HTTPS, para que el navegador confíe lo suficiente en tu origen como para permitir un service worker.
- Un web manifest que describe el nombre, los iconos y el modo de visualización de la aplicación.
- Un service worker, un script en segundo plano que intercepta las solicitudes de red y gestiona las cachés.
El resultado es una experiencia que se siente nativa, pero que sigue siendo un sitio web que puedes actualizar al instante.
El manifiesto web
El manifiesto es un archivo JSON vinculado desde el head del HTML. Le indica a la plataforma cómo presentar la aplicación una vez instalada.
{
"name": "hackweb Reader",
"short_name": "Reader",
"start_url": "/",
"display": "standalone",
"background_color": "#0b0d0c",
"theme_color": "#0b0d0c",
"icons": [
{ "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/512.png", "sizes": "512x512", "type": "image/png" }
]
}
<!-- index.html -->
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="theme-color" content="#0b0d0c" />
display: "standalone" oculta la interfaz del navegador (browser chrome) al estar instalada, mientras que los iconos y colores controlan la pantalla de inicio (splash screen) y el selector de tareas. Un manifiesto válido junto con un service worker es lo que hace que el navegador ofrezca instalar la aplicación.
El ciclo de vida del service worker
Un service worker es un script independiente que no tiene acceso al DOM. Posee un ciclo de vida propio: install, activate y fetch.
// register.js
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker.register("/sw.js");
});
}
Durante el install, realizas el pre-cacheo del app shell. Durante el activate, limpias los caches antiguos. Durante el fetch, interceptas las solicitudes y decides cómo responder. Un nuevo worker espera hasta que todas las páginas que utilizan el anterior se cierren antes de activarse, razón por la cual las actualizaciones a veces requieren una segunda recarga.
Estrategias de almacenamiento en caché
El núcleo de un service worker es su estrategia de almacenamiento en caché. Diferentes recursos requieren un tratamiento distinto.
- Cache-first — sirve desde la caché y, si falla, recurre a la red. Ideal para activos versionados e inmutables, como bundles con hash.
- Network-first — intenta obtener el recurso de la red y, si falla, recurre a la caché. Recomendado para HTML y datos que deben estar actualizados.
- Stale-while-revalidate — sirve la versión almacenada en caché inmediatamente y la actualiza en segundo plano. Excelente para datos donde es aceptable que la información esté ligeramente desactualizada.
- Cache-only y network-only — para casos de uso muy específicos.
// strategies.js
self.addEventListener("fetch", (event) => {
const { request } = event;
if (request.destination === "image") {
event.respondWith(
caches.match(request).then((cached) => cached || fetch(request)),
);
}
});
La propiedad request.destination te indica qué tipo de recurso se está solicitando, lo que te permite aplicar una estrategia según el tipo de activo.
Fallback offline
Incluso con el uso de caché, algunas solicitudes fallarán. Proporciona un fallback adecuado.
// fallback.js
self.addEventListener("fetch", (event) => {
if (event.request.mode === "navigate") {
event.respondWith(
fetch(event.request).catch(() => caches.match("/offline.html")),
);
}
});
Una página de estado offline, un shell en caché o un estado claro de “estás offline” es mucho mejor que el error predeterminado del navegador. Combinado con background sync, las solicitudes que fallaron mientras no había conexión pueden reintentarse automáticamente cuando se recupere la conectividad.
Actualizaciones y versionado
El almacenamiento en caché es potente, pero es fácil cometer errores. La regla es sencilla: asigna versiones a tus cachés y elimina las antiguas al activar. De lo contrario, los usuarios podrían quedar atrapados con archivos obsoletos indefinidamente.
// activate.js
const CACHE = "app-v2";
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))),
),
);
});
Para cualquier cosa que vaya más allá de lo básico, la librería Workbox gestiona el precaching, el enrutamiento, la expiración y la limpieza con valores predeterminados probados, y se integra con la mayoría de las herramientas de construcción.
Más allá del modo offline
Los service workers permiten hacer mucho más que el almacenamiento en caché:
- Background sync: reintenta las solicitudes fallidas cuando se recupera la conexión de red.
- Push notifications: entrega mensajes incluso cuando la aplicación está cerrada.
- Periodic sync: actualiza el contenido en segundo plano.
- Share target: permite que tu aplicación reciba contenido compartido desde el SO.
Cada una de estas funciones requiere el permiso del usuario y debe utilizarse con moderación, pero son precisamente las que hacen que una PWA se sienta como una aplicación de primer nivel.
Mejores prácticas
- Sirve el contenido a través de HTTPS y registra el worker después de la carga.
- Versiona las cachés y elimina las antiguas durante el evento activate.
- Utiliza una estrategia cache-first para assets con hash y network-first para HTML.
- Proporciona siempre un fallback para el modo offline.
- Haz precache únicamente del app shell, no de todo el sitio.
- Considera usar Workbox en lugar de escribir estrategias complejas a mano.
- Prueba el modo offline y las actualizaciones antes de desplegar a producción.
Errores comunes
- Cachear HTML indefinidamente y servir páginas obsoletas.
- Olvidar limpiar los caches antiguos después de un deploy.
- Cachear respuestas cross-origin opacas sin comprender sus limitaciones.
- Hacer precaching de todo y saturar la primera instalación.
- Asumir que el nuevo worker se activa inmediatamente.
- Ignorar la cuota de almacenamiento y permitir que los caches crezcan sin límite.
Próximos pasos
Un service worker es la mejora de rendimiento más significativa para las visitas recurrentes y la base del soporte offline. Combínalo con las técnicas de la guía de Web Performance, comprende las solicitudes HTTP que intercepta y sirve la aplicación desde un backend en Node.js. Después, añade un manifest y un worker sencillo con estrategia cache-first a un sitio existente y comprueba cómo funciona sin conexión.