Nadie quiere abrir el navegador, escribir una dirección e iniciar sesión cada vez que quiere encender una luz. El panel web de la parte 10 tiene que sentirse como una app del celular: un ícono en la pantalla de inicio, que se abre sin la barra del navegador y con la sesión guardada.
Eso es una PWA (Progressive Web App), y con Vite se consigue con un plugin. En esta parte vemos la configuración, dónde publicarla sin pagar y cómo evitar el problema más común: usuarios atrapados en una versión vieja.
Qué necesita una PWA
Para que el navegador ofrezca "instalar" la app y la abra como una app independiente hacen falta tres cosas:
- Un manifiesto web con el nombre, los colores, los íconos y el modo de visualización.
- Un service worker, un script que el navegador ejecuta aparte de la página y que puede guardar los archivos en caché.
- HTTPS.
vite-plugin-pwa genera las dos primeras a partir de la configuración de Vite, usando Workbox por debajo.
La configuración
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import { VitePWA } from "vite-plugin-pwa";
export default defineConfig({
plugins: [
react(),
VitePWA({
registerType: "autoUpdate",
injectRegister: false, // el registro se hace a mano, ver más abajo
includeAssets: ["favicon-32x32.png", "apple-touch-icon.png"],
workbox: {
globIgnores: ["**/descargas/**"], // no precachear archivos grandes que casi nadie usa
},
manifest: {
name: "Casa Demo",
short_name: "Casa",
description: "Panel de control de la casa",
lang: "es",
theme_color: "#0f766e",
background_color: "#ffffff",
display: "standalone", // se abre sin la barra del navegador
start_url: "/",
scope: "/",
icons: [
{ src: "pwa-192x192.png", sizes: "192x192", type: "image/png" },
{ src: "pwa-512x512.png", sizes: "512x512", type: "image/png" },
{ src: "pwa-512x512.png", sizes: "512x512", type: "image/png", purpose: "maskable" },
],
},
}),
],
});
Algunos detalles:
display: "standalone"es lo que quita la barra del navegador al abrir la app desde el ícono.- El ícono maskable permite que Android lo recorte en círculo o con esquinas redondeadas sin cortar el dibujo. Deja un margen generoso alrededor del dibujo. Puedes generar todos los tamaños desde un SVG con herramientas como
@vite-pwa/assets-generatoro la libreríasharp. globIgnores: Workbox guarda en caché todo lo que genera el build. Si tu app incluye archivos grandes que pocos usan (binarios, PDFs), exclúyelos: si no, cada instalación los descarga.
El iPhone necesita sus propias etiquetas
En iOS no hay un botón de "instalar": se usa Safari, Compartir → Agregar a pantalla de inicio. Para que se abra en modo app y con el ícono correcto, conviene incluir estas etiquetas en el index.html:
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<meta name="theme-color" content="#0f766e" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="Casa" />
El apple-touch-icon es un PNG de 180 × 180 píxeles.
Dónde publicarla
Una PWA es un sitio estático: archivos HTML, CSS y JavaScript. Se puede publicar gratis en Netlify, Cloudflare Pages o similares, o en S3 con CloudFront por centavos. Las diferencias, incluidas las condiciones de uso comercial de cada plan gratuito, están en dónde alojar una web app estática.
Antes de elegir, comprueba que nada ata la app a un dominio:
- Las APIs deben aceptar el origen nuevo en su configuración de CORS.
- Si usas la interfaz alojada de Cognito, las URL de retorno deben incluir el dominio nuevo. Con el SDK directo (parte 11) no hay redirecciones.
- El WebSocket de AWS IoT Core no pasa por CORS.
Y no olvides la regla de una SPA: cualquier ruta que no sea un archivo debe devolver index.html (en Netlify, /* /index.html 200 en el archivo _redirects).
Por qué una pestaña abierta no se actualiza
Con registerType: "autoUpdate" parece que la app se actualizará sola al publicar una versión nueva. Pero hay un detalle: el navegador solo busca un service worker nuevo cuando navega a la página (o, como mucho, cada 24 horas). Una pestaña abierta durante días, o una PWA que el celular mantiene en segundo plano, puede seguir ejecutando el JavaScript viejo que tiene en memoria.
El service worker nuevo puede estar listo para tomar el control (skipWaiting() y clientsClaim()), pero si nadie pregunta si hay uno nuevo, no llega.
La solución: registrar el service worker a mano y preguntar
Desactiva el registro automático (injectRegister: false, como en la configuración de arriba) y registra con el hook del propio plugin, agregando una comprobación periódica:
import { useRegisterSW } from "virtual:pwa-register/react";
const BUSCAR_ACTUALIZACION_MS = 30 * 60 * 1000; // cada 30 minutos
export default function App() {
useRegisterSW({
onRegisteredSW(_swUrl, registration) {
if (!registration) return;
setInterval(() => registration.update(), BUSCAR_ACTUALIZACION_MS);
},
});
// ...
}
Con registerType: "autoUpdate", el hook recarga la página en cuanto detecta un service worker nuevo. Lo que faltaba era volver a preguntar mientras la app sigue abierta.
Para que TypeScript reconozca el módulo virtual, agrega sus tipos en tsconfig.app.json:
{
"compilerOptions": {
"types": ["vite-plugin-pwa/client"]
}
}
Si prefieres no recargar sin avisar (por ejemplo, si el usuario podría estar escribiendo algo), usa registerType: "prompt" y muestra un botón de "Hay una versión nueva" con el valor needRefresh del mismo hook.
Comprobar que todo funciona
- En Chrome de escritorio: DevTools → Application → Manifest muestra si el manifiesto es válido y si la app es instalable; Service workers muestra cuál está activo.
- En Android: Chrome ofrece "Instalar app" en el menú.
- En iPhone: Safari → Compartir → Agregar a pantalla de inicio. Al abrirla desde el ícono no debería verse la barra de Safari.
- Para probar la actualización: publica una versión con un cambio visible, deja la app abierta y comprueba que se actualiza sola en el siguiente ciclo de comprobación.
Preguntas frecuentes
¿La app funciona sin internet?
Abre, porque los archivos están en caché, pero no puede controlar nada: el control pasa por la nube. Para una app de domótica en la nube, el beneficio real de la PWA es la instalación y la apertura rápida, no el modo sin conexión.
¿Por qué no publicar en la App Store?
Porque una PWA se instala sin revisión de Apple ni cuenta de desarrollador, y se actualiza al instante. A cambio, en iOS tiene limitaciones, como las notificaciones, que solo funcionan con la app instalada en la pantalla de inicio.
¿Cada cuánto conviene buscar actualizaciones?
Depende de cuánto te importe que todos usen la última versión. Treinta minutos es un buen equilibrio: casi no genera tráfico y ninguna pestaña queda desactualizada por días.