4dim / Notas

El telón de carga: de 3.068 a 132 milisegundos

El telón aparecía después de lo que debía tapar, porque esperaba a la hoja de estilos que venía a ocultar. El orden del head, esperar una señal y no un reloj, el rectángulo que cubría el sitio sin JavaScript, y la segunda visita de 9 segundos a 47 milisegundos.

Un telón que llegaba tarde

Si tienes una web y te importa lo que ve quien entra con mala conexión, esta nota va de eso. Cuenta cómo un adorno pensado para que la página se viera mejor era justo lo que retrasaba todo.

El sitio tiene un telón de carga: una capa del color del fondo que tapa la página mientras llegan los estilos y las fuentes, para que no se vea el HTML desnudo ni un instante. La idea es razonable. La ejecución, medida, era absurda.

Con una red lenta simulada, el primer pintado de la página ocurría a los 3.068 milisegundos, y el telón se veía entre el segundo 3,07 y el 4,53. O sea, el telón aparecía después de lo que debía tapar.

La causa estaba en el orden del <head>, la parte del HTML donde se declara lo que la página necesita. La hoja de estilos, 42 kB, iba antes que el telón. Una hoja de estilos frena el pintado de todo lo que viene detrás, así que el navegador no pintaba el telón hasta descargar y procesar esos 42 kB. El telón esperaba al CSS que venía a ocultar.

El orden del <head>

La corrección fue reordenar. El telón y su estilo van primero, escritos dentro del propio HTML. La hoja de estilos se anuncia con preload as="style" y se aplica cuando llega.

Medida (red simulada, 3 s por recurso)AntesDespués
Primer pintado3.068 ms132 ms
Telón visible desde3.070 ms14 ms

Veintitrés veces más rápido en empezar a mostrar algo, sin tocar una línea de la aplicación. Solo el orden de cinco etiquetas.

Esperar una señal, no un reloj

El telón se retiraba cuando el documento y las fuentes estaban listos, con un tope de 1,5 segundos por si acaso. En red lenta ese tope se cumplía antes de que llegara el CSS. El telón se iba y dejaba la página sin estilo. Un reloj no sabe si lo que espera llegó.

Ahora espera una señal de verdad: que la hoja de estilos se haya aplicado. Las fuentes llevan font-display: swap, así que el texto se lee con una letra del sistema mientras llegan las propias.

Y el tope duro subió a 8 segundos. Ya no significa «por si el CSS tarda», sino «por si algo se rompió».

Un indicador de carga que se retira por tiempo miente dos veces: cuando se va antes de que llegue lo que esperaba, y cuando se queda después.

Sin JavaScript, un rectángulo para siempre

Mientras medíamos, encontramos algo peor. El telón se pintaba siempre, con display: flex puesto de entrada, y solo JavaScript lo retiraba.

Con JavaScript desactivado, bloqueado o fallando, el telón cubría el sitio entero para siempre, encima de un HTML que el servidor había entregado perfectamente. Nadie lo había notado porque nadie navega sin JavaScript. Excepto algunos rastreadores, algunos lectores y algunas redes corporativas.

Y lo encontramos dos veces. Semanas después, una revisión de cómo se veía el sitio en pantallas pequeñas descubrió que la bandera que debía autorizar el telón desde JavaScript se escribía, pero nunca se leía. El mismo rectángulo, por otra grieta.

La regla que quedó: el telón solo se pinta si una línea de JavaScript en el <head> lo autoriza. Sin JavaScript no hay telón, y el HTML del servidor se ve tal cual.

La pestaña en segundo plano

Otro fallo del mismo telón. Al volver a la pestaña después de haber estado en otra, se quedaba congelado. Eran tres causas a la vez.

El atributo que lo apagaba vivía en un nodo que React recreaba al refrescar, y se perdía. Los setTimeout, los avisos programados para rescatarlo, ya habían corrido: los navegadores frenan los temporizadores de las pestañas que nadie está mirando. Y document.fonts.readypuede no resolverse nunca en una pestaña que no llega a dibujarse.

La solución tuvo dos partes. El atributo de control se movió a <html>, fuera del alcance de React. Y además del reloj se escuchan visibilitychange, que no se frena, y pageshow, que salta al volver con el botón atrás.

Lo verificamos sin navegador, con un DOM de juguete que ejecuta los scripts reales.

La segunda visita: de 9 segundos a 47 milisegundos

Con la misma red simulada, la segunda visita al sitio tardaba unos 9 segundos en tenerlo todo. Le pusimos un service worker, un programa que vive dentro del navegador y guarda copias de los archivos que no cambian.

La segunda visita pasó a 47 milisegundos, con cada recurso servido desde el disco del visitante en 12 a 30 ms. Guarda solo scripts, estilos y fuentes marcados con su huella, que son públicos y nunca cambian.

Nunca guarda las rutas con sesión iniciada. Servir ahí una copia vieja no es lentitud: es enseñar lo que no toca.

Qué hacer entonces

  • Medir con red lenta simulada. En fibra todo parece bien.
  • Poner primero en el <head> lo que debe verse primero, escrito ahí mismo, antes de cualquier hoja de estilos.
  • Retirar los indicadores de carga por señal, no por tiempo. El tiempo es solo el último recurso.
  • Probar el sitio con JavaScript desactivado una vez al mes. Lo que aparezca es lo que ven los rastreadores.
  • Escuchar visibilitychange y pageshow además de load.

Esta nota sale de nuestro trabajo en Desarrollo web.

← todas las notas