Stairway Logo STAIRWAY

Cómo crear PWA en Jekyll

Publicado el 22 de October, 2026

La PWA  (Progressive Web App) se utiliza para permitir que una web o catálogo funcione como una aplicación nativa instalable en iOS y Android sin pasar por las tiendas de aplicaciones (App Store / Play Store), ofreciendo experiencia móvil optimizada y funcionamiento sin conexión (offline).

Pasos de implementación técnica


convertir una web estática en PWA requiere 5 elementos clave:
  1. Iconos de la aplicación: en formato PNG. : Guardar las imágenes en la ruta de recursos (por ejemplo, en assets/images/icons/ en tamaños 192x192 y 512x512 píxeles).
    • assets/images/icons/icon-192.png
      assets/images/icons/icon-512.png
  2. Manifiesto de la aplicación ( manifest.json ): Crear el archivo en el root, con el Front Matter de Jekyll de encabezado.
    • ---
      layout: null
      ---
      {
        "name": "San Martín Pesca y Caza",
        "short_name": "San Martín",
        "start_url": "{{ '/' | relative_url }}",
        "display": "standalone",
        "background_color": "#ffffff",
        "theme_color": "#1a1a1a",
        "description": "Tienda especializada en equipamiento de pesca, caza y artículos de camping.",
        "icons": [
          {
            "src": "{{ '/assets/images/icons/icon-192.png' | relative_url }}",
            "sizes": "192x192",
            "type": "image/png"
          },
          {
            "src": "{{ '/assets/images/icons/icon-512.png' | relative_url }}",
            "sizes": "512x512",
            "type": "image/png"
          }
        ]
      }
    • display: Definido como "standalone" para que abra sin la barra del navegador.
  3. Service Worker ( sw.js ): es para gestionar el almacenamiento en caché de los recursos estáticos y permitir el funcionamiento fuera de línea
    • ---
      layout: null
      ---
      
      const CACHE_NAME = 'san-martin-pwa-v1';
      const URLS_TO_CACHE = [
        "{{ '/' | relative_url }}",
        "{{ '/offline.html' | relative_url }}",
        "{{ '/assets/css/style.css' | relative_url }}",
        "{{ '/manifest.json' | relative_url }}"
      ];
      
      // Instalación del Service Worker y almacenamiento en caché de los recursos base
      self.addEventListener('install', (event) => {
        event.waitUntil(
          caches.open(CACHE_NAME).then((cache) => {
            return cache.addAll(URLS_TO_CACHE);
          })
        );
        self.skipWaiting();
      });
      
      // Activación y limpieza de cachés antiguas
      self.addEventListener('activate', (event) => {
        event.waitUntil(
          caches.keys().then((cacheNames) => {
            return Promise.all(
              cacheNames.map((cache) => {
                if (cache !== CACHE_NAME) {
                  return caches.delete(cache);
                }
              })
            );
          })
        );
        self.clients.claim();
      });
      
      // Estrategia de respuesta: Network First con respaldo en caché
      self.addEventListener('fetch', (event) => {
        if (event.request.method !== 'GET') return;
      
        event.respondWith(
          fetch(event.request)
            .then((networkResponse) => {
              // Guarda una copia actualizada en la caché
              const responseToCache = networkResponse.clone();
              caches.open(CACHE_NAME).then((cache) => {
                cache.put(event.request, responseToCache);
              });
              return networkResponse;
            })
            .catch(() => {
              // En caso de estar sin conexión, responde desde la caché
              return caches.match(event.request).then((cachedResponse) => {
                if (cachedResponse) {
                  return cachedResponse;
                }
                // Si la página solicitada no está en caché, muestra offline.html
                if (event.request.headers.get('accept') && event.request.headers.get('accept').includes('text/html')) {
                  return caches.match("{{ '/offline.html' | relative_url }}");
                }
              });
            })
        );
      });
  4. Inclusión en _config.yml : Para garantizar que Jekyll no omita el archivo manifest.json durante el proceso de build, asegúrate de declararlo o incluirlo en la lista de archivos explícitos de tu _config.yml
    • include:
        - admin/config.yml
        - manifest.json
  5. Vinculación y Registro en la Plantilla HTML:
    • Abre tu diseño principal (por ejemplo _layouts/default.html o la plantilla en uso) e inyecta la etiqueta meta del tema y la vinculación del manifiesto dentro de la sección <head>:

    • <!-- Configuración PWA -->
      <link rel="manifest" href="{{ '/manifest.json' | relative_url }}">
      <meta name="theme-color" content="#1a1a1a">
      <link rel="apple-touch-icon" href="{{ '/assets/images/icons/icon-192.png' | relative_url }}">
  • Luego, añade el script para registrar el Service Worker justo antes del cierre de la etiqueta </body>:
<script>
  if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
      navigator.serviceWorker.register("{{ '/sw.js' | relative_url }}")
        .then(reg => console.log('Service Worker registrado con éxito:', reg.scope))
        .catch(err => console.log('Error al registrar Service Worker:', err));
    });
  }
</script>


Página Offline (offline.html)


Crea un archivo llamado offline.html en la raíz del proyecto para responder cuando el usuario no tenga conexión a internet y la página solicitada no haya sido almacenada en la caché previamente:
--- 
layout: default 
title: "Sin Conexión"
--- 
<div> 
<h1>
📶 Sin conexión a Internet
</h1> 
<p>Actualmente no tienes conexión a la red. Las páginas que ya has visitado siguen estando disponibles.
</p> 
</div>