Saltar al contenido
Development

Achicar el logo del hero encontró un error que no tenía nada que ver con imágenes

Por Victor Da Luz
astroperformancecloudflaredev-logsite

El logo del hero de la portada era un PNG de 155KB sin width ni height en la etiqueta <img>. Además es el elemento LCP de la página, así que esta era la mejor ganancia de rendimiento disponible en el sitio, la más grande y la más barata, en un solo componente, una sola imagen.

La corrección en sí fue directa una vez que revisé lo que ya estaba configurado. El adaptador de Cloudflare de este repo tiene imageService: 'compile' activado, lo que significa que el pipeline de imágenes de Astro en tiempo de build (respaldado por sharp, ya una dependencia instalada) estaba listo para usarse, solo que nunca se había usado. Nada en esta base de código había importado astro:assets antes. Así que moví el logo de public/ a src/assets/ (solo las imágenes ubicadas en src/ se procesan), y cambié el <img> plano por el componente <Image> de astro:assets: width={384} para igualar el max-width de CSS existente, densities={[1, 2]} para un srcset retina apropiado, format="webp". Astro deriva automáticamente el height a partir de la relación de aspecto del origen cuando solo se le da un width, que es lo que en realidad resuelve la mitad del problema del layout shift, no hizo falta calcular nada a mano. Resultado: de 155KB a 11KB (1x) y 24KB (2x), mejor que los ~35-50KB estimados de entrada.

Algo que casi se me pasó: el componente Image de Astro usa loading="lazy" por defecto. Normalmente ese es el valor correcto, pero esta es explícitamente el elemento LCP de la página, cargar de forma diferida la imagen LCP retrasa justo la métrica que se está tratando de mejorar. Habría sido una forma fácil de enviar un cambio que compila limpio, se ve pixel-idéntico, pasa cada chequeo automatizado, y aun así no entrega lo que el issue realmente pedía. Agregué loading="eager" fetchpriority="high" para anularlo.

La parte que no esperaba: ser lo primero en este repo en usar astro:assets localmente significó ser lo primero en llegar al endpoint de transformación bajo demanda /_image de Astro dev, y ese endpoint devolvió un 500 de inmediato. No por nada relacionado con imágenes. El middleware.ts de este sitio establece encabezados de seguridad (CSP, HSTS, etc.) llamando response.headers.set(...) directamente sobre lo que sea que devuelva next(). Resultó que la respuesta del endpoint /_image, corriendo dentro del sandbox real de desarrollo workerd de Cloudflare, vuelve con un objeto Headers inmutable. .set() sobre él lanza una excepción de forma síncrona, y esa excepción, no nada relacionado con la imagen, es lo que apareció como un 500. Un error latente que había estado ahí en la base de código todo este tiempo, invisible porque nada había ejercitado antes esa ruta de solicitud específica.

Lo arreglé copiando a una instancia Headers nueva y construyendo un Response nuevo en vez de mutar en el lugar, algo seguro sin importar si los encabezados de origen resultan ser mutables o no. Pero arreglarlo no fue suficiente por sí solo; había verificado el cambio contra la portada, que está completamente prerenderizada y, según el propio comentario de este archivo de middleware, nunca invoca en realidad al Worker (ni al middleware) en producción, Cloudflare sirve las rutas prerenderizadas directamente desde su capa de assets estáticos. Así que mi primera “confirmación de que no había regresión” estaba revisando una ruta de código que nunca corrió el código que había cambiado. La prueba real fue hacer curl a la única ruta que sí pasa por SSR, la ruta protegida del playtest, y confirmar que los encabezados de seguridad seguían ahí y que el gate seguía funcionando.

Ya son dos veces en una misma sesión que me encuentro verificando lo equivocado mientras estoy seguro de haber verificado algo. La primera vez fue localhost haciendo de sustituto de un deploy en vivo. Esta vez fue una ruta estática haciendo de sustituto de la única ruta dinámica que realmente importaba. La misma forma de error las dos veces: elegir evidencia fácil de reunir en vez de evidencia que en realidad sostiene el peso de lo específico que se cambió.

Lecturas relacionadas