Introducción: este artículo explica, con todo lujo de detalles, cómo convertir un sitio WordPress en una Progressive Web App (PWA) usando un manifest.json y un Service Worker para modo offline. Incluye ejemplos de código listos para copiar, pasos para integrar en un theme o plugin, estrategias de caché, manejo de contenido dinámico (REST API), manejo de actualizaciones y pruebas con las herramientas del navegador.
Contents
Requisitos previos
- Servidor con HTTPS (obligatorio para Service Workers).
- Acceso al sistema de archivos del sitio (FTP/SFTP o editor de temas) o capacidad para crear un plugin propio.
- Preferiblemente usar un child theme o un plugin personalizado para no perder cambios con actualizaciones.
- Conocimientos básicos de PHP, JavaScript y estructura de WordPress.
Resumen del flujo
- Crear manifest.json y los iconos PWA.
- Registrar el manifest en el ltheadgt del tema.
- Crear una página offline (offline.html) y colocarla en la raíz o en una ruta accesible.
- Crear sw.js (Service Worker) en la raíz (o en carpeta y ajustar scope) con estrategia de caché.
- Registrar el Service Worker desde el frontend con un pequeño script encolado por WordPress.
- Probar y depurar con DevTools y Lighthouse.
1) Manifest.json: qué es y ejemplo
El manifest es un archivo JSON que describe la PWA al navegador: iconos, color de tema, pantalla de inicio, nombre y scope. Debe estar disponible en la raíz (por ejemplo https://tusitio.com/manifest.json) o en una ruta accesible por todos los usuarios.
{ name: Mi Sitio WordPress, short_name: MiSitio, description: Sitio WordPress convertido a PWA con modo offline, start_url: /?utm_source=homescreen, scope: /, display: standalone, orientation: portrait, background_color: #ffffff, theme_color: #0073aa, icons: [ { src: /icons/icon-192.png, sizes: 192x192, type: image/png }, { src: /icons/icon-512.png, sizes: 512x512, type: image/png } ] }
Campos clave del manifest
name | Nombre completo de la app. |
short_name | Nombre corto mostrado en la pantalla de inicio. |
start_url | URL que se abre cuando el usuario inicia la app desde el icono. |
scope | Rango de rutas controladas por la PWA (normalmente /). |
display | standalone elimina la UI del navegador, minimal-ui u otros valores según necesidad. |
theme_color / background_color | Color de la UI del navegador y pantalla de carga. |
icons | Iconos en múltiples tamaños (192px y 512px recomendados). |
2) Iconos y ubicación
Crea una carpeta /icons/ en la raíz del sitio (o en tu tema y ajustar rutas). Genera al menos:
- icon-192.png (192×192)
- icon-512.png (512×512)
- favicon y otras variantes si quieres (ico, 180×180 para Apple).
Recomendación: usar herramientas como RealFaviconGenerator o ImageMagick para generar las variantes automáticamente.
Añade la referencia al manifest y los meta tags para theme-color y Apple (si quieres soporte básico iOS). Insertar desde functions.php o un plugin personalizado.
// En functions.php de tu child theme o en plugin personalizado add_action(wp_head, function() { echo ltlink rel=manifest href=/manifest.jsongtn echo ltmeta name=theme-color content=#0073aagtn // Meta para Apple (iOS) si lo deseas (iOS no usa manifest pero reconoce algunos metadatos) echo ltlink rel=apple-touch-icon href=/icons/icon-192.pnggtn echo ltmeta name=apple-mobile-web-app-capable content=yesgtn })
4) Crear página offline: offline.html
Una página HTML sencilla que el Service Worker ofrecerá cuando no haya conexión. Colócala en la raíz o en la carpeta pública.
lt!doctype htmlgt lthtml lang=esgt ltheadgt ltmeta charset=utf-8gt ltmeta name=viewport content=width=device-width,initial-scale=1gt lttitlegtSin conexiónlt/titlegt ltstylegt body{font-family:Arial,Helvetica,sans-seriftext-align:centerpadding:2rembackground:#f7f7f7color:#333} .card{max-width:500pxmargin:autopadding:1.5rembackground:#fffborder-radius:8pxbox-shadow:0 2px 8px rgba(0,0,0,.06)} lt/stylegt lt/headgt ltbodygt ltdiv class=cardgt lth1gtEstás sin conexiónlt/h1gt ltpgtParece que no tienes acceso a Internet. Puedes navegar contenido ya visitado o volver cuando la conexión esté disponible.lt/pgt ltpgtlta href=/gtVolver al iniciolt/agtlt/pgt lt/divgt lt/bodygt lt/htmlgt
5) Service Worker (sw.js): ejemplo completo y explicaciones
Coloca sw.js en la raíz (recomendado) para que su scope por defecto abarque todo el sitio. Ejemplo simple con precache y estrategias diferenciadas (cache-first para assets, network-first para API/HTML):
const CACHE_VERSION = v1 const PRECACHE = precache-{CACHE_VERSION} const RUNTIME = runtime-{CACHE_VERSION} // Lista de recursos a precachear (ajusta según tu tema) const PRECACHE_URLS = [ /, // inicio /offline.html, /wp-includes/css/dist/block-library/style.min.css, /wp-content/themes/tu-theme/style.css, /icons/icon-192.png, /icons/icon-512.png ] self.addEventListener(install, event => { event.waitUntil( caches.open(PRECACHE).then(cache => cache.addAll(PRECACHE_URLS)).then(() => self.skipWaiting()) ) }) self.addEventListener(activate, event => { // Limpiar caches antiguos const currentCaches = [PRECACHE, RUNTIME] event.waitUntil( caches.keys().then(keys => Promise.all( keys.map(key => { if (!currentCaches.includes(key)) { return caches.delete(key) } }) ) ).then(() => self.clients.claim()) ) }) self.addEventListener(fetch, event => { const request = event.request const url = new URL(request.url) // No gestionar peticiones a terceros (CDNs con CORS si no quieres) if (url.origin !== location.origin) { return } // API REST de WordPress: network-first (intentar obtener la versión más reciente) if (url.pathname.startsWith(/wp-json/)) { event.respondWith( fetch(request).then(networkResponse => { return caches.open(RUNTIME).then(cache => { cache.put(request, networkResponse.clone()) return networkResponse }) }).catch(() => caches.match(request)) ) return } // HTML navegacional: network-first con fallback a cache y offline.html if (request.mode === navigate (request.headers.get(accept) ).includes(text/html)) { event.respondWith( fetch(request).then(response => { // Guardar en caché runtime return caches.open(RUNTIME).then(cache => { cache.put(request, response.clone()) return response }) }).catch(() => { return caches.match(request).then(match => match caches.match(/offline.html)) }) ) return } // Para assets estáticos: cache-first event.respondWith( caches.match(request).then(cachedResponse => { if (cachedResponse) return cachedResponse return caches.open(RUNTIME).then(cache => fetch(request).then(response => { // Solo cachear respuestas válidas (status 200) if (response response.status === 200 request.method === GET) { cache.put(request, response.clone()) } return response }) ) }) ) })
Explicación de la estrategia
- Precache: recursos esenciales que deben estar disponibles offline (index, offline.html, css, icons).
- Runtime cache (RUNTIME): recursos almacenados bajo demanda durante la navegación.
- Network-first para rutas HTML y REST API: intenta obtener contenido fresco y, si falla, usa caché.
- Cache-first para assets: sirve rápido desde caché y cachea peticiones nuevas.
- Control de versiones con CACHE_VERSION para invalidar cachés antiguos al actualizar.
6) Registrar el Service Worker en frontend (script encolado por WordPress)
Encolar un pequeño script que registre sw.js cuando el navegador soporte Service Workers. Usa wp_enqueue_script o inline script desde functions.php.
add_action(wp_enqueue_scripts, function() { // Registrar un archivo JS simple o insertar inline wp_add_inline_script(jquery, if (serviceWorker in navigator) { window.addEventListener(load, function() { navigator.serviceWorker.register(/sw.js) .then(function(reg) { // Registro correcto console.log(Service Worker registrado:, reg) }) .catch(function(error) { console.log(Error al registrar Service Worker:, error) }) }) } ) })
7) Manejo de contenido dinámico: caché de REST API y formularios
Para endpoints REST (wp-json) usa network-first para entregar contenido actualizado, pero almacena en caché la respuesta en caso de caída de red.
Para formularios (contacto, envío) no cachees las peticiones POST. Diseña la UX para que los envíos se realicen en línea o uses background sync (apoya en Workbox o en APIs experimentales) para reintentar envíos cuando la conexión vuelva.
8) Actualización del Service Worker
- Cambiar CACHE_VERSION fuerza la eliminación de caches antiguos en activate().
- Usa self.skipWaiting() en install() y self.clients.claim() en activate() para activar el nuevo SW inmediatamente (cuidado, puede interrumpir sesiones activas).
- Enviar notificación a los usuarios de que hay una nueva versión disponible (por ejemplo, un snackbar) y permitir recargar la página para activar el nuevo SW.
9) Testing y depuración
- Chrome DevTools gt Application: verificar manifest, Service Worker y almacenamiento (Caches).
- Simular offline en DevTools para comprobar offline.html y cachés.
- Usar Lighthouse para auditar PWA revisa errores y advertencias y corrígelos (HTTPS, manifest válido, iconos, start_url, offline readiness).
10) Integración avanzada: Workbox
Para proyectos más complejos, Workbox (Google) facilita generar Service Workers con estrategias avanzadas, precaching automático durante build y plugins para manejar rutas y expiraciones.
Si usas un workflow con Webpack/Gulp, puedes generar sw.js con Workbox y versionar automáticamente las rutas precacheadas.
11) Errores comunes y soluciones
- No se registra el SW: asegúrate de estar en HTTPS y que la ruta /sw.js devuelva 200 y con encabezado de tipo correcto (text/javascript).
- Scope limitado: si colocas sw.js en un subdirectorio, su scope estará limitado. Para cubrir todo el sitio, ponlo en la raíz.
- Manifest no detectado: verifica que la ruta en ltlink rel=manifest href=/manifest.jsongt sea accesible y que el JSON sea válido (Content-Type: application/json no es obligatorio pero recomendable).
- Conflictos con plugins de caché: algunos plugins de caché pueden alterar rutas o excluir /sw.js o /manifest.json. Excluye estos archivos de minificación/optimización o configura reglas para que se sirvan sin modificaciones.
- iOS: Safari en iOS tiene soporte parcial para PWA. No soporta Service Workers en versiones antiguas y tiene limitaciones en el almacenamiento incluye meta tags Apple y un icono apple-touch-icon para mejorar la experiencia.
12) Checklist final antes de lanzar
- HTTPS activo en todo el sitio.
- manifest.json accesible y válido.
- Iconos en varios tamaños presentes y rutas correctas.
- sw.js en la ubicación esperada y con la estrategia de caché adecuada.
- offline.html accesible y precacheada.
- Registro de SW encolado en el frontend.
- Pruebas completadas con DevTools y Lighthouse (score PWA>90 recomendado).
- Notas de compatibilidad para iOS y navegadores antiguos documentadas.
13) Ejemplo de plugin mínimo (esqueleto) para integrar manifest y sw
Notas finales y buenas prácticas
Transformar un sitio WordPress en PWA aporta ventajas de rendimiento y disponibilidad offline, pero requiere diseño cuidadoso: elegir qué caché, cómo actualizar contenido dinámico y cómo tratar interacciones (formularios, búsquedas). Mantén siempre archivos críticos (manifest, sw, offline.html, icons) fuera de minificaciones o cambios automáticos de plugins. Versiona el cache y prueba la actualización del SW en un entorno de staging antes de lanzarlo a producción.
Lista rápida de recursos útiles
- lta href=https://developers.google.com/web/fundamentals/app-install-bannersgtPWA - Google Developerslt/agt
- lta href=https://developer.mozilla.org/en-US/docs/Web/Progressive_web_appsgtMDN - Progressive Web Appslt/agt
- lta href=https://developers.google.com/web/tools/lighthousegtLighthouse para auditar PWAlt/agt
- lta href=https://developers.google.com/web/tools/workboxgtWorkbox para Service Workers avanzadoslt/agt
|
Acepto donaciones de BAT's mediante el navegador Brave :) |