Cuaderno · Nota 001 · Rendimiento web

Dónde se escondían los últimos 18 puntos en una web estática

Imágenes comprimidas, JavaScript en tres archivos y ningún framework. Lighthouse seguía dando 82. Los puntos que faltaban estaban en tres sitios que la herramienta señalaba, pero no explicaba.

Serhat Belen

Fundador, BLN Global · · 7 min

Respuesta breve

Las imágenes y JavaScript ya rendían bien. Los puntos restantes estaban en tres sitios. Uno: la comprobación creada para evitar trabajo innecesario leía el diseño y obligaba al navegador a recalcularlo. Dos: la hoja de estilos y las fuentes externas retrasaban 600 ms el primer renderizado. Insertamos el CSS en la página y alojamos las fuentes. Tres: 6 de las 24 alertas de contraste eran falsos positivos. El rastreo completo descubrió después 13 páginas que la muestra no había detectado.

Este sitio no usa ningún framework. Las páginas son HTML puro generado con scripts de Python. Los estilos están en un solo archivo CSS y JavaScript ocupa tres archivos pequeños. Las imágenes se generan en WebP y en tres tamaños. Todo lo evidente para mejorar el rendimiento ya estaba hecho.

La nota móvil se quedaba en 82.

Tras resolver lo evidente, los puntos restantes no estaban en un gran fallo, sino repartidos entre tres sitios. Para encontrarlos no bastaba con leer el aviso de la herramienta. Había que entender qué escondía el aviso qué quiere decir exigía entenderlo.

Primer sitio: la propia comprobación creada para saltarse el trabajo

El aviso de «reflujo forzado» (forced reflow) de Lighthouse indica que JavaScript obliga al navegador a calcular el diseño antes de tiempo. El navegador acumula los cambios de estilo para aplicarlos de una vez. Si entre medias se consulta una medida, tiene que procesarlos al instante.

El aviso marcaba la línea fuente, pero allí no había ninguna medida visible. Salió en la tercera ronda.

  • En la primera ronda getComputedStyle quitamos las llamadas. La nota no cambió.
  • En la segunda ronda apareció el culpable real: un código escrito para saltarse trabajo inútil if (window.scrollY > 0) comprobación. La propia comprobación lee la disposición de la página. La línea escrita para evitar ese trabajo obligaba a hacerlo.
  • En la tercera ronda borramos la función. El estado inicial ya estaba definido en CSS. JavaScript no tenía que recrearlo.

Queda otro molde void element.offsetWidth era. Un truco común y aceptado: fuerza el diseño a propósito para reiniciar una transición. En su lugar, dos requestAnimationFrame se añadió. Hace lo mismo sin forzar la maquetación.

function yenidenBaslat(fn) { /* Si la pestaña está en segundo plano, rAF no se ejecuta y la demo queda vacía. */ if (document.hidden) { fn(); return; } requestAnimationFrame(function () { requestAnimationFrame(fn); }); }
Reiniciar la transición sin forzar el diseño. Las pestañas en segundo plano necesitan una salida: allí rAF no se ejecuta.

La lección fue clara: un aviso de rendimiento no suele decir «haces demasiado», sino «lo haces en el momento equivocado».

Segundo sitio: los 600 ms previos al primer renderizado

El segundo aviso señalaba recursos que bloqueaban el primer renderizado. Al abrir la página, el navegador no dibujaba nada hasta descargar y procesar la hoja de estilos y las fuentes. Medimos un retraso de 600 milisegundos.

El CSS quedó dentro de la página

Una hoja CSS separada llega desde la caché en la segunda visita. Eso suma. En la primera, en cambio, añade una petición extra, y esa primera visita es la del usuario que viene desde resultados de búsqueda. Al publicar, reducimos el CSS y lo metemos en la página. Lo medimos: con Brotli, incrustado pesó menos que archivo aparte más segunda petición.

Servimos las fuentes desde nuestro servidor

La instalación de Google Fonts conectaba con dos orígenes distintos: uno para el estilo fonts.googleapis.com, para los archivos fonts.gstatic.com. Cada origen nuevo exige resolver el DNS, negociar TLS y abrir otra conexión. Al eliminarlo medimos esto:

250 KB165 KB

Fuentes que descarga una página en turco: 3 archivos desde un solo origen, en vez de 6 archivos y 2 orígenes externos.

Cómo lo medimos el CSS de Google Fonts se descargó con una identidad real de Chrome, unicode-range mirando esos campos, separamos los subconjuntos que una página en turco bajaría de verdad (latin + latin-ext) y medimos cada archivo. El otro lado, en este repositorio, assets/fonts tamaño real de los archivos.

Cómo redujimos las fuentes y qué coste tuvo abrirlas a diez idiomas mereció una nota aparte: diez idiomas, tres archivos de fuentes.

Tercer punto: donde falla la herramienta

La auditoría de accesibilidad mostró 24 avisos de contraste. Corregirlos todos habría estropeado medio diseño. 6 eran falsos.

Motivo content-visibility:auto era. Esta función acelera el primer renderizado al posponer las zonas que quedan fuera de pantalla. Justo lo que buscábamos. El problema es que el color calculado de esa zona no llegaba a la herramienta de auditoría, que avisaba de «blanco sobre blanco». En pantalla no ocurría.

Medimos uno a uno los contrastes reales en el navegador. 18 avisos eran ciertos y los corregimos. Los otros 6 eran un punto ciego de la herramienta: content-visibility solo se quitó de las dos secciones problemáticas. En el resto se mantuvo.

La alerta de una herramienta de auditoría no prueba que haya un error que corregir. Es una afirmación. Primero se comprueba, luego se corrige. Esta lección volvió a salir dos veces en este sitio. Una fue en la nota de revisión lingüística se cuenta.

Y una herida nuestra

para evitar saltos de diseño, a todas las imágenes width y height Se añadió el atributo. Bien visto: el navegador reserva el hueco de la imagen sin descargarla. Pero rompió las cajas de demo: algunas imágenes se dibujaron con 58 píxeles de ancho y 1500 píxeles de alto.

La razón: en el HTML width y height los atributos orientan la presentación y en CSS height rellenan el valor. En el diseño, para esas cajas solo aspect-ratio estaba definido, height estaba vacío. El atributo se colocó ahí y hundió la proporción. Arreglo de una línea:

img{max-width:100%;height:auto;display:block}
Los atributos siguen en su sitio y evitan el cambio de layout. La altura la corrige CSS.

Aquí es donde el rastreo sí sirve

Con la home en 100, parecía cerrado. No lo estaba. Lo que medíamos era una muestra. El sitio sus 150 páginas pasaron una a una por Lighthouse y salió esto: 137 páginas con 100 en las cuatro áreas, no 13 páginas. La accesibilidad, las buenas prácticas y el SEO daban 100 en todas las páginas. Solo fallaba el rendimiento.

Once de trece páginas estaban en el mismo idioma: árabe. La más baja, 83.

/ar/safecoast/ 83 /ar/laya/ 84 /ar/localmind/ 84 /ar/adforge/ 85 /ar/garanti/ 85 /ar/gtin/ 86 /ar/visionguard/ 86 /ar/ 89 /ar/growthos/ 97 /ar/dream/ 99 /ar/twin/ 99
Páginas en árabe tras el rastreo completo. Las dos desviaciones restantes (/pl/modelmesh/ y /ru/terms.html, ambas con 98) entran en la variación normal entre mediciones.

Lighthouse dio la razón sin rodeos: Fuente web cargada → ar.woff2. La fuente árabe llegaba tarde y recomponía la página. Como la fuente de reserva árabe medía muy distinto a Noto Sans Arabic, el salto salía grande. Lo arreglamos con la misma decisión que ya habíamos tomado en Martian Mono: swap no optional.

0,2870

Cambio de diseño (CLS) en páginas árabes

Cómo lo medimos Tras el cambio, volvimos a medir 15 páginas en árabe: las 15 tenían CLS 0. Las puntuaciones de rendimiento subieron de 83-89 a 98-100 y 9 de las 15 páginas llegaron a 100. Medimos el sitio en producción con Lighthouse.

La verdadera lección no trata sobre rendimiento. Tomar una muestra y decir «el sitio tiene 100» implica afirmar algo sobre páginas que no has medido. Por eso, quince páginas siguieron durante meses con 83 puntos. Nunca las revisamos.

El límite del resultado

La puntuación sale de una prueba de laboratorio. Presupone un dispositivo y una red fijos. El teléfono y la conexión de una visita real pueden rendir peor. Al medir la misma página varias veces, el resultado oscila entre 98 y 100. Sacar 100 no significa «rápida para todos», sino «sin obstáculos medibles». Cuando haya suficientes datos reales, habrá que mirar los datos de campo de Chrome.

Las tres correcciones tenían algo en común: no pedían trabajar más. Quitaban trabajo hecho a destiempo o trabajo que sobraba desde el principio.

Preguntas frecuentes

¿Qué quiere decir Lighthouse con «reflujo forzado»?

Que JavaScript obligue al navegador a calcular antes de tiempo los cambios de estilo acumulados. El navegador los agrupa y aplica de una vez. Si entre medias lees una medida, tiene que procesarlos al instante. offsetWidth, getBoundingClientRect, scrollY y getComputedStyle todas esas lecturas lo disparan.

¿Siempre conviene insertar el CSS en la página?

No. Un archivo separado llega desde la caché en la segunda visita. Eso ayuda. Incrustarlo acelera la primera visita y frena las repetidas. Tienes que decidir según el sitio: si pesa más el usuario que entra desde resultados de búsqueda, incrustar compensa. Lo medimos en nuestro caso y compensó.

¿Dejo de usar content-visibility?

No. Retrasar el procesamiento de lo que queda fuera de pantalla sí acelera el primer renderizado. La función no falla. La herramienta de auditoría no sabe leer el color de los bloques aplazados. Basta con desactivarla donde genera falsos avisos.

¿Sacar 100 puntos significa que la web es rápida?

No. Es una medición de laboratorio que presupone un dispositivo y una red fijos. Al medir la misma página varias veces seguidas, el resultado oscila entre 98 y 100. El dato real aparece en los datos de campo de Chrome, que tardan en acumularse.

¿Por qué hay que medir todas las páginas en vez de tomar una muestra?

En nuestro caso, 13 de 150 páginas ni salieron en la muestra y 11 estaban en el mismo idioma. La home marcaba 100, pero las páginas en árabe se quedaban en 83. Si no mides una página, no puedes opinar sobre ella.

Fuentes

Trabajos relacionados