Como convertir el sitio en PWA con manifest y modo offline en WordPress

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

  1. Crear manifest.json y los iconos PWA.
  2. Registrar el manifest en el ltheadgt del tema.
  3. Crear una página offline (offline.html) y colocarla en la raíz o en una ruta accesible.
  4. Crear sw.js (Service Worker) en la raíz (o en carpeta y ajustar scope) con estrategia de caché.
  5. Registrar el Service Worker desde el frontend con un pequeño script encolado por WordPress.
  6. 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.

3) Incluir el manifest y meta tags en el ltheadgt

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

  1. 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).
  2. Scope limitado: si colocas sw.js en un subdirectorio, su scope estará limitado. Para cubrir todo el sitio, ponlo en la raíz.
  3. 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).
  4. 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.
  5. 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 :)



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *