Pensando en Performance

Niv Oksenberg
Justo Tech-blog
Published in
4 min readMay 12, 2021
ilustración por Consuelo Nuñez
Ilustración de Consuelo Nuñez.

En un mercado tecnológico altamente competitivo, los productos no sólo se diferencian por funcionalidades o características, sino que también por calidad. La experiencia de usuario se degrada no sólo por interfaces poco amigables, sino también por flujos que se sienten lentos. Digo sienten porque no hay una métrica absoluta, lo que manda es la percepción del usuario.

Entonces, intuitivamente, la performance de una web app es importante, pero, ¿qué tan importante? Algunos casos de estudio ayudan a entender esto un poco mejor:

  • Vodafone redujo su LCP (largest contentful paint) en un 31%, elevando sus ventas un 8% en su canal digital [1].
  • Yelp redujo su FCP (first contentful paint) en un 45% y aumentó su conversión en un 15% [2].
  • COOK incrementó su conversión en un 7% al reducir su tiempo de carga en 0.85 segundos [3].

Si bien estos porcentajes pueden parecer pequeños, Vodafone y Yelp son compañías que transan en bolsa con revenues que se cuentan en billions. En el caso de COOK, lo notable es que los usuarios reaccionan a diferencias de tiempo de menos de un segundo.

Lo interesante (y complejo) de hablar de performance, es que basta con tener un componente en el sistema que esté armando un cuello de botella para degradar las métricas más importantes. Por lo mismo, mantener un performance budget [4] requiere muchas veces del trabajo conjunto de todo el equipo de tecnología. Hay mucho que tiene que ver con la cultura de desarrollo [5].

Hablemos un poco más de lo técnico y ciertas acciones que tomamos en Justo. El flujo que se desencadena cuando un usuario entra a una de nuestras casi 3000 tiendas es más o menos el siguiente:

  1. El navegador del usuario comienza el proceso de resolución DNS de la URL, obteniendo una IP.
  2. Comienza el proceso de negociación TLS para mantener una comunicación HTTPs segura entre el navegador y un web server interno. Un detalle importante es que HTTP/2 sólo funciona sobre TLS.
  3. El navegador va a buscar un archivo entry point a una CDN. Aquí hay referencias a otros archivos estáticos (Javascript y CSS) que se dividen en chunks que se sirven exactamente cuando se necesitan.
  4. El navegador carga nuestro frontend hecho en React y pide más información, principalmente de dos tipos:

a. Imágenes.

b. Información específica de la tienda. Esta comunicación es con nuestro backend.

Cada uno de estos pasos entra dentro de nuestra cadena crítica en la performance percibida por el usuario. El nivel de especificidad de algunas etapas parece un poco arbitrario, pero los dividí así porque en cada paso hacemos cosas interesantes que valdría la pena comentar.

Sería difícil hablar de todo en profundidad en un mismo post, pero partamos conversando del punto 4.a.

Las imágenes son fundamentales en nuestra UX. Es fácil perder el interés en una tienda que no muestra imágenes de manera rápida. Es difícil estar interesado en un producto sin verlo. Por lo mismo, en algunas de nuestras websites las imágenes constituyen más de un 50% de la información que es necesaria cargar. Algo importante a tener en cuenta, es que nuestros clientes (o partners, como les decimos realmente 😍) suben sus propias imágenes. Si hay fotos profesionales de mucho peso, es nuestra responsabilidad cargarlas de manera eficiente.

La mejor forma de pensar este punto es que el navegador necesita descargar las imágenes. Aquí hay dos variables importantes:

  1. El tiempo que toma contactar a la fuente que tiene las fotos.

Este punto es simple de mitigar. Lo más razonable es usar una CDN. En nuestro caso, al usar Cloudfront, la distancia física de las fuentes de información a nuestros usuarios es relativamente pequeña. A la fecha, hay edge servers en Chile, México, Colombia, Brasil y Argentina [6], por lo que esta etapa tarda un pequeño número de milisegundos.

2. El tiempo que demora al cliente recibir (descargar) la imagen.

Aquí el problema se torna más complejo, especialmente porque en promedio el 70% de nuestros usuarios entran desde su celular, llegando a un 85% en ciertas zonas. En una conexión 3G, una imagen de 1MB puede demorar 3 segundos o más en descargarse.

Algunas estrategias que nos han resultado:

Servir imágenes en formatos más eficientes. Al transformar una foto JPG a formato WEBP hemos visto una disminución de tamaño de un 30% en promedio. Ya estamos probando con fotos en AVIF, que ofrecen mejoras de hasta un 50%.

Usar imágenes en su resolución correcta. Las fotos que se usan en mobile no tienen (ni deben) ser las mismas que en Desktop. Haciendo reducciones de resolución podemos disminuir el peso hasta en un 65%, sin pérdida de calidad. Aquí hacemos transformaciones dinámicas que dependen del tamaño del viewport del usuario y de las dimensiones fijas que le asignamos a las imágenes dentro del HTML (importante para evitar layout shifts, más de esto en otro post). Cada versión se guarda directamente en la CDN, así servimos la imagen óptima para cada dispositivo sin delays.

Cargar las imágenes en el tiempo correcto. En el primer render no es necesario cargar imágenes que se encuentren en el footer de la página. Midiendo qué es lo que ve el usuario en su viewport (usando la API del intersection observer y algunas cosas más), podemos decidir cuándo cargar cada imagen (i.e. lazy loading). Esto, por ejemplo, nos permite darle más prioridad a los recursos críticos que nos llevan al primer render, que de otra manera estarían compitiendo con las imágenes.

Para cerrar, hablar de performance es hablar de arquitectura web, de computación distribuida y de optimización de componentes en cada parte del stack. Pero por otro lado, también es pensar en experiencia de usuario, comportamientos humanos y la salud del negocio detrás del producto.

¡Más información en posts futuros!

--

--