4dim / Notas

El service worker que no cachea la consola

La segunda visita pasó de 9 segundos a 47 milisegundos. Pero la regla más importante es qué no se cachea nunca: la lista negra que pudo servir la página de un negocio a otro, la lista blanca que la sustituyó, la identidad del build y el interruptor de emergencia.

Qué acelera y qué no debe tocar

Si tu web tiene una parte pública y otra con contraseña, esta nota te dice qué conviene guardar en el navegador del visitante y qué no.

Un service worker es un programa pequeño que el navegador guarda y que se pone en medio de las peticiones del sitio. Sirve para que la segunda visita salga de la caché del propio visitante: con red lenta simulada, pasamos de unos 9 segundos a 47 milisegundos.

Y sirve para romper el sitio de una forma que ningún despliegue arregla, porque el script roto ya vive en el navegador de quien visitó.

La regla más importante no es qué cachear, sino qué no. La consola, que es la parte con sesión y con datos de un negocio, no se cachea nunca, ni para acelerar. Servir ahí una copia vieja no es lentitud: es enseñar lo que no toca.

De lista negra a lista blanca

La primera versión cacheaba todo excepto una lista de rutas de la consola. Funcionaba mientras el sitio público y la consola eran dos aplicaciones distintas.

Cuando pasaron a ser una sola, esa lista negra tendría que crecer con cada ruta nueva de la consola. Y olvidarse de ampliarla no daría un error: daría la página de un negocio servida desde la caché a otro negocio. Una fuga de datos entre inquilinos por una lista que alguien no actualizó.

Se invirtió a lista blanca: no se cachea nada, salvo los recursos públicos que están enumerados. Las rutas de la consola quedan fuera aunque nadie se acuerde de ellas. Es la misma lección que ya nos había costado una lista de rutas en nginx, el servidor que atiende el tráfico: que el olvido falle del lado seguro.

En una caché, «todo menos lo prohibido» convierte cada olvido en una fuga. «Nada menos lo permitido» convierte cada olvido en una petición sin acelerar. Solo uno de los dos errores se puede permitir.

Dos estrategias según el nombre lleve hash

Los recursos que salen del build llevan en el nombre un hash del contenido, una especie de huella: si el contenido cambia, el nombre cambia. Esos se sirven de caché sin revalidar nunca, porque por definición ya no cambian.

Otros tienen nombre fijo y contenido que cambia con cada despliegue, como el CSS y los scripts del sitio público. Esos van con «servir de caché y reponer»: se entrega lo guardado al momento y se pide la versión nueva en segundo plano.

La cabecera que indica la revisión del sitio se sirve con no-cache, para que un navegador con la página abierta se entere de que hubo despliegue.

Cada despliegue borra todas las cachés de versiones anteriores, sin intentar conservar nada. El sitio pesa poco, y una caché mezclada de dos revisiones es justo el estado que nadie sabe depurar.

Un blindaje contra un fallo que aún no existe

La regla que cachea páginas HTML se limita a peticiones con mode === "navigate", es decir, a navegaciones de documento: cuando el navegador pide una página entera.

Hoy no haría falta. Pero si un día Next empezara a pedir datos a esas mismas URL para moverse entre pantallas sin recargar, el service worker serviría el HTML cacheado donde se esperaban datos. El fallo aparecería días después y en frío.

Preferimos anticipar el cambio del framework y cerrar esa puerta antes de que exista.

La identidad del build

El service worker necesita saber de qué versión es, para invalidar la caché vieja. Ese dato se generaba con git rev-parse, hasta que el despliegue empezó a copiar el código a la ranura de producción sin el directorio .git. Entonces dejó de funcionar.

Ahora se lee de .next/BUILD_ID, un archivo que Next genera solo y que sobrevive a la copia. Lo lee un módulo compartido que también usa el endpoint de salud, la dirección que se consulta para saber si el sitio está vivo.

Ese endpoint, por cierto, devolvió "build": null durante meses, porque leía una variable de entorno que nadie definía. Un campo siempre nulo es peor que ninguno.

El interruptor de emergencia

Un service worker roto se queda puesto en los navegadores que ya lo tienen, y no hay despliegue que lo alcance: el propio script decide cuándo actualizarse.

Por eso el nuestro lleva, desde antes de necesitarlo, una orden que lo desregistra a sí mismo y borra todas sus cachés. Se dispara visitando cualquier página con ?sw=fuera al final de la dirección. Es la única forma de recuperar a esos visitantes sin pedirles que borren a mano los datos del navegador.

Qué hacer entonces

  • Lista blanca de lo público e inmutable. Nada con sesión, jamás.
  • Hash en el nombre, caché eterna; nombre fijo, servir y reponer.
  • Limitar la caché de HTML a navegaciones de documento.
  • Sacar la identidad del build del artefacto, no de git.
  • Poner el interruptor de emergencia antes del primer despliegue del service worker, no después del primer incidente.

Esta nota sale de nuestro trabajo en Desarrollo web.

← todas las notas