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:
- 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
- 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.
- 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 }}"); } }); }) ); });
- 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
- 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 }}">
- 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>:
- 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>