Este tutorial es una continuación de uno en el que hablábamos de conceptos básicos relacionados con el rendimiento de nuestras aplicaciones web. En este nos centraremos en entender las diferentes mejoras que podemos hacer en nuestros sitios web para optimizar el rendimiento.
- HTML
- Camino crítico
- CSS
- JavaScript
- Asistencia al navegador
- Rendimiento de las imágenes
- Video
- Fuentes web
- Web workers
- Conclusiones
HTML
¿Cómo mejorar el performance en nuestro HTML?
El primer paso para construir un sitio web que cargue rápidamente es recibir una respuesta oportuna del servidor para el HTML de una página. Cuando se introduce una URL en la barra de búsqueda de nuestro navegador, este envía una solicitud GET al servidor para recuperar nuestra página. La primera solicitud de una página web es para obtener un recurso HTML, y una de las claves que nos van a permitir asegurar un buen rendimiento es conseguir que nos llegue rápidamente y con los mínimos retrasos.
Minimizar redirecciones
Cuando se hace una petición para solicitar un recurso, el servidor puede responder con un redireccionamiento. Estos redireccionamientos reducen la velocidad de carga de la página, ya que requieren que el navegador haga una solicitud adicional a la nueva página para recuperar el recurso.
Existen dos tipos de redireccionamiento:
- Redirecciones de mismo origen: Estas ocurren dentro de tu origen, por lo que están a tu control, ya que toda la lógica de los mismos está dentro de tu servidor web.
- Redirecciones entre origenes: Están suelen estar iniciadas por otro origen, y está fuera de tu control. Este tipo de redireccionamientos suelen ser utilizados por anuncios, servicios de acortamiento de URL y otros servicios de tercero.
Cachear respuestas HTML
Guardar en memoria la respuesta HTML tiene bastantes puntos positivos como que haríamos menos solicitudes al servidor. Con esto optimizaríamos el uso de recursos y obtendríamos una mejor experiencia de usuario, ya que la carga de recursos sería más rápida. Pero puede ser arriesgado, ya que puede incluir enlaces a otros recursos, como podrían ser CSS, Javascript, imágenes, …etc. Esto sería un problema, ya que en caso de que hubiera un actualización en nuestro sitio web, el usuario podría recibir contenido desactualizado, y la gestión de caché para los desarrolladores podría ser un desafío.
A pesar de contar con este tipo de problemas, se pueden aplicar diferentes soluciones, una buena estrategia sería la de contar con una duración de caché corta, puede tener beneficios como permitir que un recurso se cachee en una CDN, reduciendo la cantidad de solicitudes que se sirven desde el servidor de origen, y en el navegador, permitiendo que los recursos se revaliden en lugar de descargarlos nuevamente.
Este enfoque funciona mejor para contenido estático que no cambia en ningún contexto, y se puede establecer un tiempo apropiado para cachear los recursos a algún número de minutos que consideres adecuado. Cinco minutos para recursos HTML estáticos es una apuesta segura y garantiza que las actualizaciones periódicas no pasen desapercibidas.
Si el contenido de una página web está personalizado de alguna manera, como podría ser para un usuario autenticado, puede que cachear el contenido no sera la mejor de nuestras opciones. En este tipo de casos es mejor evitar cachear el HTML.
Medir tiempos de respuesta del servidor
Medir los tiempos de respuesta del servidor es crucial para evaluar y mejorar el rendimiento de un sitio web. La latencia del servidor puede tener un impacto significativo en la experiencia del usuario y en el rendimiento general del sitio. Con las herramientas y técnicas adecuadas, compo podría ser, por ejemplo Google Analytics, se pueden identificar y mitigar problemas de rendimiento, asegurando una experiencia de usuario óptima y un sitio web eficiente y escalable.
Compresión
Las respuestas basadas en texto, como HTML, JavaScript, CSS e imágenes SVG, deben comprimirse para reducir su tamaño de transferencia a través de la red y así descargarlas más rápidamente. Los algoritmos de compresión más utilizados son gzip y brotli.
Se recomienda hacer uso de brotli siempre que sea posible, ya que proporciona un algoritmo de compresión bastante favorable y es compatible con los navegadores modernos. En caso de que nuestro sitio web pueda ser visitado por navegadores más antiguos, se puede usar gzip como alternativa.
La compresión generalmente se configura automáticamente por la mayoría de los proveedores de alojamiento web, pero hay algunas cosas importantes a considerar si estás en una posición para configurar o ajustar la configuración de compresión tú mismo.
Existen dos tipos de compresión, la dinámica y la estática. Cada una con un enfoque diferente de cuando se debe comprimir un recurso.
- Compresión dinámica: comprime el recurso en el momento en el que se solicita. Puede añadir latencia debido al tiempo de compresión en el momento de la solicitud, aunque es beneficioso para contenido que varía frecuentemente.
- Compresión estática: comprime los recursos antes de antemano, evitando el así realizar la compresión en el momento de la solicitud. Con esta estrategia de compresión dinámica conseguimos evitar ese tiempo de latencia a la hora de realizar al compresión, como ocurre con la compresión dinámica.
Los recursos estáticos, como Javascript, CSS, imágenes…etc deben comprimirse estáticamente. Por otro lado, los recursos HTML, al generarse dinámicamente en algunos casos, deberían comprimirse dinámicamente.
Redes de distribución de contenido (CDN)
Una Red de Distribución de Contenido (CDN) es una red distribuida de servidores que almacenan en caché recursos desde tu servidor de origen, y a su vez, los sirven desde servidores de borde que están físicamente más cerca de tus usuarios. La proximidad física a tus usuarios reduce el tiempo de ida y vuelta (RTT), mientras que las optimizaciones como HTTP/2 o HTTP/3, el almacenamiento en caché y la compresión permiten que la CDN sirva contenido más rápidamente que si se recuperara de tu servidor de origen. Utilizar una CDN puede mejorar significativamente el Time To First Byte (TTFB) de tu sitio web en algunos casos.
Camino crítico
Por camino crítico nos referimos a los pasos involucrados hasta que la página web comienza a renderizarse en el navegador. Para renderizar una página, los navegadores necesitan un documento HTML, junto a una serie de recursos críticos para renderizar ese documento.
Renderizado progresivo
El camino crítico de renderizado es el proceso que sigue el navegador para convertir el HTML, CSS y JavaScript en píxeles visibles en la pantalla. Entender este proceso es vital para optimizar y mejorar la velocidad de carga y la experiencia del usuario. A continuación, se describen los pasos involucrados en el camino crítico de renderizado y las estrategias para optimizarlo.
- Construcción del DOM (Document Object Model): El navegador descarga el archivo HTML y lo convierte en un DOM, que es una representación en memoria de la estructura del documento HTML.
- Construcción del CSSOM (CSS Object Model): El navegador descarga y analiza los archivos CSS y los convierte en un CSSOM, que es una representación en memoria de las reglas de estilo.
- Construcción del Render Tree: El navegador combina el DOM y el CSSOM para crear el Render Tree. Este árbol contiene solo los nodos que se necesitan para renderizar la página y excluye aquellos que están ocultos (por ejemplo, con display: none).
- Layout (Reflow): El navegador recorre el Render Tree y calcula la geometría de cada nodo (posición y tamaño). Este proceso se conoce como «layout» o «reflow».
- Painting: El navegador convierte los elementos del Render Tree en píxeles en la pantalla. Este proceso se conoce como «painting».
Una vez se hayan completado estos pasos, el usuario será capaz de ver contenido en el sitio web.
¿Qué recursos son críticos en el renderizado inicial?
Para comenzar con la renderización inicial, el navegador necesita de algunos recursos críticos.
- Parte del HTML.
- CSS bloqueador de renderizado en el elemento <head>.
- JavaScript bloqueador de renderizado en el elemento <head>.
El navegador procesa el HTML de manera secuencial. Tan pronto como el navegador obtiene alguna parte del HTML de una página, el navegador comienza a procesarlo. El navegador puede entonces decidir renderizarlo mucho antes de recibir el resto del HTML de la página.
El elemento <head> es clave para procesar el camino crítico de renderizado. Optimizar el contenido del elemento <head> es un aspecto clave del rendimiento web. El elemento <head> contiene metadatos sobre la página y sus recursos, pero el contenido que hay dentro de este elemento no es algo que el usuario pueda ver. El contenido visible se encuentra dentro del elemento que sigue al elemento <head>. Antes de que el navegador pueda renderizar cualquier contenido, necesita tanto el contenido para renderizar como los metadatos sobre cómo renderizarlo.
Sin embargo, no todos los recursos referenciados en el elemento <head> son estrictamente necesarios para la renderización inicial de la página, por lo que el navegador solo espera aquellos que lo son. Para identificar qué recursos están en el camino crítico de renderizado, es necesario enteder el CSS y JavaScript bloqueador de renderizado y bloqueador de análisis.
Recursos bloqueadores de renderizado
Algunos recursos se consideran tan críticos que el navegador pausa la renderización de la página hasta que los ha manejado. Uno de estos recursos críticos es el CSS. Cuando un navegador ve CSS, ya sea CSS en línea en un elemento style, o un recurso referenciado externamente especificado por un elemento el navegador evita renderizar más contenido hasta que haya completado la descarga y el procesamiento de ese CSS.
Que un recurso bloquee el renderizado no significa que detenga al navegador de hacer cualquier otra cosa. Los navegadores intentan ser lo más eficientes posible, así que cuando un navegador ve que necesita descargar un recurso CSS, lo solicitará y pausará el renderizado, pero seguirá procesando el resto del HTML y buscará otro trabajo que hacer mientras tanto.
Recursos bloqueadores de análisis
Los recursos bloqueadores de análisis son aquellos que impiden que el navegador busque otro trabajo al continuar analizando el HTML. JavaScript por defecto es bloqueador de análisis (a menos que esté marcado específicamente como asíncrono o diferido), ya que JavaScript puede cambiar el DOM o el CSSOM tras su ejecución. Por lo tanto, no es posible que el navegador continúe procesando otros recursos hasta que conozca el impacto completo del JavaScript solicitado en el HTML de una página.
Los recursos bloqueadores de análisis son efectivamente bloqueadores de renderizado también. Dado que el analizador no puede continuar más allá de un recurso bloqueador de análisis hasta que se haya procesado completamente, no puede acceder ni renderizar el contenido después de él. El navegador puede renderizar cualquier HTML recibido hasta el momento mientras espera, pero en lo que respecta al camino crítico de renderizado, cualquier recurso bloqueador de análisis en el <head> efectivamente significa que todo el contenido de la página está bloqueado para ser renderizado.
Bloquear al analizador puede tener un costo de rendimiento más que solo bloquear el renderizado. Por esta razón, los navegadores intentarán reducir este costo utilizando un analizador HTML secundario conocido como escáner de precarga para descargar recursos futuros mientras el analizador HTML principal está bloqueado.
Este escaner de precarga es una optimización del navegador en forma de un analizador HTML secundario que escanea la respuesta HTML sin procesar para encontrar y buscar especulativamente recursos antes de que el analizador HTML principal los descubra de otra manera. Por ejemplo, el explorador de precarga permitiría al navegador comenzar a descargar un recurso especificado en un elemento <img>, incluso cuando el analizador HTML está bloqueado mientras se obtienen y procesan recursos como CSS y JavaScript.
¿Cómo podemos identificar a estos recursos bloqueantes?
Existen muchas herramientas de auditoría de rendimiento identifican recursos bloqueadores de renderizado y análisis, como podrían ser WebPageTest o Lighthouse.
CSS
El CSS determina la presentación y el diseño de una página. Como se ha descrito antes, se trata de un recurso que bloquea el renderizado, por lo que optimizar tu CSS podría tener un impacto considerable en el tiempo de carga total de la página.
Minificación
La minificación de archivos CSS reduce el tamaño del archivo de un recurso CSS, lo que hace que se descarguen más rápido. Esto se logra principalmente eliminando contenido de un archivo CSS fuente, como espacios y otros caracteres invisibles, y produciendo el resultado en un archivo nuevo y optimizado:
No Minificado
Minificado
En su forma más básica, la minificación de CSS es una optimización efectiva que podría mejorar el FCP de tu sitio web, e incluso el LCP en algunos casos. Herramientas como los empaquetadores pueden realizar automáticamente esta optimización por ti en las compilaciones de producción.
Eliminar CSS no utilizado
Antes de renderizar cualquier contenido, el navegador necesita descargar y analizar todas las hojas de estilo. El tiempo requerido para completar el análisis también incluye estilos que no se usan en la página actual. Si estás utilizando un empaquetador que combina todos los recursos CSS en un solo archivo, es probable que tus usuarios estén descargando más CSS del necesario para renderizar la página actual. Para descubrir CSS no utilizado en un sitio web, se puede hacer uso de por ejemplo la herramienta de Cobertura en las Herramientas para Desarrolladores de Chrome.
Eliminar CSS no utilizado tiene un efecto doble: además de reducir el tiempo de descarga, estás optimizando la construcción del árbol de renderizado, ya que el navegador necesita procesar menos reglas de CSS.
Evita las declaraciones CSS @import
Aunque pueda parecer conveniente, deberías evitar las declaraciones @import en CSS
@import url('style.css');
Al igual que funciona el elemento <link> en HTML, la declaración @import en CSS te permite importar un recurso CSS externo desde dentro de una hoja de
estilos. La principal diferencia es que el elemento HTML <link> es parte de la respuesta HTML y, por lo tanto, el navegador lo descubre mucho antes que un archivo CSS descargado por una declaración @import.
La razón de esto es que para que una declaración @import sea descubierta, primero se debe descargar el archivo CSS que la contiene. Esto resulta en lo que se conoce como una cadena de solicitudes que, en el caso de CSS, retrasa cuánto tiempo tarda una página en renderizarse inicialmente. Otro inconveniente es que las hojas de estilo cargadas usando una declaración @import no pueden ser descubiertas por el explorador de precarga y, por lo tanto, se convierten en recursos de bloqueo de renderizado descubiertos tarde.
Por lo que la manera en la que se recomendaría hacerlo sería esta
link rel="stylesheet" href="style.css"
En la mayoría de los casos, puedes reemplazar la @import mediante el uso de un elemento <link rel="stylesheet">
. Los elementos <link> permiten que las hojas de estilo se descarguen concurrentemente y reducen el tiempo de carga total, en lugar de las declaraciones @import, que descargan hojas de estilo de manera consecutiva.
JavaScript
JavaScript es una parte fundamental de las aplicaciones web modernas, pero puede tener un impacto significativo en el rendimiento si no se gestiona adecuadamente. Enviar demasiado JavaScript puede hacer que tu página web sea lenta para responder durante la carga de la página, y puede incluso causar problemas de capacidad de respuesta que ralentizan las interacciones. Ambas cosas pueden ser frustrantes para los usuarios.
Cuando se cargan elementos script sin los atributos defer o async, el navegador bloquea el análisis y la renderización hasta que se descarga, analiza y ejecuta el script. De manera similar, los scripts en línea bloquean el analizador hasta que se analiza y ejecuta el script.
async vs defer
Los atributos de async async y defer permiten que los scripts externos se carguen sin bloquear el análisis HTML mientras que los scripts (incluidos los scripts en línea) con type="module"
se diferencian automáticamente. Sin embargo, async y defer tienen algunas diferencias que son importantes de entender.
- async: Los scripts cargados con async se analizan y ejecutan inmediatamente una vez descargados.
- defer: los scripts cargados con defer se ejecutan cuando se termina el análisis del documento HTML, esto ocurre al mismo tiempo que el evento DOMContentLoaded del navegador. Además, los scripts async pueden ejecutarse fuera de orden, mientras que los scripts defer se ejecutan en el orden en que aparecen en el marcado.
Renderizado en el lado del cliente
Generalmente, se debe evitar el uso de JavaScript para renderizar cualquier contenido crítico o el elemento LCP de una página. Esto se conoce como renderizado en el lado del cliente, y es una técnica utilizada extensamente en las Aplicaciones de Página Única (SPAs).
El marcado renderizado por JavaScript evita el escáner de precarga, ya que los recursos contenidos dentro del marcado renderizado por el cliente no son detectados por él. Esto podría retrasar la descarga de recursos cruciales, como una imagen LCP. El navegador solo comienza a descargar la imagen LCP después de que se ha ejecutado el script y ha añadido el elemento al DOM. A su vez, el script solo puede ejecutarse después de que ha sido descubierto, descargado y analizado. Esto se conoce como una cadena de solicitudes críticas y debe evitarse.
Además, renderizar el marcado usando JavaScript es más probable que genere tareas largas que el marcado descargado del servidor en respuesta a una solicitud de navegación. El uso extensivo de renderizado del HTML en el lado del cliente puede afectar negativamente la latencia de interacción. Esto es especialmente cierto en casos donde el DOM de una página es muy grande, lo que desencadena un trabajo de renderizado significativo cuando JavaScript modifica el DOM.
Minificación
Similar al CSS, la minificación de JavaScript reduce el tamaño del archivo de un recurso de script. Esto puede llevar a descargas más rápidas, lo que permite que el navegador pase al proceso de análisis y compilación de JavaScript más rápidamente. Además, la minificación de JavaScript va un paso más allá que la minificación de otros activos, como CSS. Cuando se minifica JavaScript, no solo se eliminan cosas como espacios, tabulaciones y comentarios, sino que también se acortan los símbolos en el JavaScript fuente.
Si estás utilizando un empaquetador para procesar el código fuente de tu sitio web, este proceso mencionado anteriormente a menudo se hace automáticamente para las compilaciones de producción. Estos minificadores son altamente configurables, lo que te permite ajustar la agresividad del algoritmo de minificación para lograr ahorros máximos.
División de código JavaScript
Se de una técnica de optimización que consiste en dividir el código JavaScript de una aplicación web en múltiples archivos más pequeños y separados cargar solo lo necesario en el momento adecuado, en lugar de tener un solo archivo JavaScript grande. Durante la carga inicial de la página, el exceso de ejecución y análisis de JavaScript puede ralentizar la experiencia del usuario, especialmente porque los usuarios tienden a interactuar con la página en este momento.
La técnica de spliteo de código permite dividir el paquete de JavaScript en dos partes:
- El JavaScript necesario durante la carga inicial de la página, que no puede cargarse en otro momento.
- El JavaScript restante, que puede cargarse en un momento posterior, generalmente cuando el usuario interactúa con algún elemento interactivo en la página.
Para implementar el spliteo de código, se utiliza la sintaxis de import() dinámicos en lugar de los elementos script
tradicionales. Esta sintaxis realiza una solicitud de recursos de JavaScript en un momento posterior durante el ciclo de vida de la página, en lugar de durante el inicio. Por ejemplo, un módulo de validación de formulario puede descargarse, analizarse y ejecutarse solo cuando un usuario interactúa con un campo de entrada en un formulario.
Herramientas de empaquetado de JavaScript como webpack, Parcel, Rollup y esbuild pueden configurarse para dividir automáticamente los paquetes de JavaScript en fragmentos más pequeños cuando encuentran una llamada de import()
dinámica en el código fuente. Es importante destacar que algunas herramientas, como esbuild, requieren que optes por esta optimización de forma explícita.
Asistencia al navegador
En esta sección veremos técnicas con las que podemos ayudar al navegador a cargar los diferentes recursos de un sitio web, esto lo conseguiremos gracias el uso de pistas de recursos.
Las pistas de recursos instruyen al navegador a como realizar ciertas acciones con antelación que podrían mejorar el rendimiento de carga. Gracias a estas conseguiremos optimizar el tiempo de carga de la página informando al navegador en como cargar y priorizar los recursos. Pueden realizar acciones como realizar búsquedas DNS tempranas, conectar a servidores con antelación e incluso buscar recursos antes de que el navegador los descubra de manera ordinaria.
Las pistas de recursos pueden especificarse en HTML, a menudo al principio en el elemento <head>, o establecerse como una cabecera HTTP.
preconnect
La pista preconnect se utiliza para establecer una conexión con otro origen desde donde estás obteniendo recursos críticos. Por ejemplo, es posible que estés alojando tus imágenes o activos en un CDN u otro origen cruzado:
link rel="preconnect" href="https://example.com"
Al utilizar preconnect, anticipas que el navegador tiene previsto conectarse a un servidor de origen cruzado específico en un futuro muy próximo, y que el navegador debería abrir esa conexión lo antes posible, idealmente antes de esperar a que el analizador HTML o el escáner de precarga lo hagan.
Si tienes una gran cantidad de recursos de origen cruzado en una página, utiliza preconnect para aquellos recursos que sean más críticos para la página actual.
Un caso de uso común para preconnect son las fuentes de Google. Google Fonts recomienda que te preconectes al dominio https://fonts.googleapis.com que sirve las declaraciones @font-face y al dominio https://fonts.gstatic.com que sirve los archivos de fuentes.
link rel="preconnect" href="https://fonts.googleapis.com";
link rel="preconnect" href="https://fonts.gstatic.com" crossorigin;
El atributo crossorigin se utiliza para indicar si un recurso debe recuperarse utilizando el Compartir Recursos de Origen Cruzado (CORS). Cuando usas la pista preconnect, si el recurso que se está descargando desde el origen utiliza CORS, como los archivos de fuentes, entonces necesitas agregar el atributo crossorigin a la pista preconnect.
dns-prefecth
El dns-prefetch es una técnica de optimización del rendimiento web que permite al navegador realizar búsquedas DNS de manera anticipada, lo que ayuda a reducir el tiempo necesario para resolver nombres de dominio a direcciones IP. Esto es particularmente útil cuando una página web necesita cargar recursos desde múltiples servidores de origen cruzado.
dns-prefetch no establece una conexión a un servidor de origen cruzado, sino que solo realiza la búsqueda DNS por adelantado. Una búsqueda DNS ocurre cuando se resuelve un nombre de dominio a su dirección IP subyacente. Aunque las capas de cachés DNS a nivel de dispositivo y de red ayudan a que este proceso sea generalmente rápido, aún lleva cierta cantidad de tiempo.
link rel="dns-prefetch" href="https://fonts.googleapis.com">
link rel="dns-prefetch" href="https://fonts.gstatic.com">
A diferencia de preconnect, que puede consumir más recursos al abrir conexiones anticipadas, dns-prefetch es menos costoso y puede ser usado con más frecuencia sin impacto significativo en los recursos del navegador. Otro punto importante es que es especialmente útil para optimizar la navegación hacia otros sitios web, mejorando la experiencia del usuario al seguir enlaces externos.
preload
La directiva preload se utiliza para iniciar una solicitud temprana de un recurso necesario para renderizar la página. Esta técnica permite que los recursos críticos se carguen antes, mejorando así el rendimiento y la experiencia del usuario.
link rel="preload" href="/font.woff2" as="font" crossorigin
Al cargar recursos críticos de manera anticipada, preload ayuda a reducir el tiempo de carga total de la página. Esto es especialmente útil para elementos que afectan el Largest Contentful Paint (LCP), una métrica clave de rendimiento. Otro punto es que al asegurar que recursos esenciales, como fuentes y estilos críticos, estén disponibles lo antes posible, se mejora la velocidad de renderizado y la percepción de carga rápida por parte del usuario.
prefetch
La directiva prefetch se utiliza para iniciar una solicitud de baja prioridad para un recurso que probablemente se utilizará en futuras navegaciones. Esta técnica permite que el navegador descargue recursos anticipadamente, mejorando así la experiencia del usuario en visitas subsiguientes.
link rel="prefetch" href="/example.css" as="style"
Esta directiva sigue en gran medida el mismo formato que la directiva preload. Sin embargo, a diferencia de la directiva preload, prefetch es en gran medida especulativa en el sentido de que estás iniciando una búsqueda de un recurso para una navegación futura que puede o no ocurrir.
Se trata de una herramienta poderosa para mejorar el rendimiento percibido de una aplicación web al anticipar y cargar recursos para futuras navegaciones. Esto resulta en una experiencia de usuario más rápida y fluida, especialmente en sitios donde se pueden prever patrones de navegación específicos.
API de prioridad de búsqueda
La API de Prioridad de Búsqueda permite a los desarrolladores web ajustar la prioridad con la que se descargan los recursos. Esto se realiza mediante el atributo fetchpriority, que puede ser utilizado en elementos , <img> y script
. Esta API es particularmente útil para optimizar la carga de recursos críticos en una página web.
Por defecto, las imágenes se descargan con una prioridad baja y, si están en el viewport inicial, su prioridad se incrementa automáticamente. Sin embargo, utilizando fetchpriority="high"
, se puede indicar al navegador que ciertos recursos deben descargarse con alta prioridad desde el principio, mejorando así el tiempo de carga del contenido más relevante.
asignar diferentes prioridades a diferentes recursos, se puede controlar mejor el orden y la velocidad con la que se descargan. Esto es especialmente útil para optimizar el rendimiento percibido por el usuario, asegurando que los recursos más importantes se carguen primero.
Rendimiento de las imágenes
Las imágenes suelen ser los recursos más pesados en los sitios web. Por lo que optimizarlas nos va a permitir mejorar de manera significativa el rendimiento. En la mayoría de casos, optimizar las imágenes vas a significar reducir el número de bytes de descarga, también se van a poder reducir los bytes ofreciendo al usuario una imagen con un tamaño específico en base al tipo dispositivo que el usuario esté utilizando para visionar la página web.
srcset
srcset es un atributo de <img> que permite especificar una lista de imágenes que va a usar el navegador en base a un parámetro denominado DPR (Device Client Hint), que vendría a ser el ratio de píxeles del dispositivo que está utilizando el usuario o a un ancho. Cada imagen de la lista debe incluir la url de la imagen en sí, junto a un ancho o el DPR.
En el código mostrado podemos ver un ejemplo de como se haría uso de este atributo, la imagen dentro del srcset se descargará en vez de la indicada en el src en dispositivos de alta resolución.
En cambio, la imagen referenciada en src será un candidato a descargarse en dispositivo de resolución inferior, y sería como si tuviera un 1x.
sizes
La solución propuesta anteriormente solo funciona en caso de que muestres la imagen con el mismo tamaño en todos los viewport. Pero en muchos casos, en base al tipo de dispositivo, la estructura de la página va a cambiar, junto con el tamaño del contenedor de las imágenes que tengamos.
El atributo sizes permite establecer un conjunto de fuentes de tamaños, donde cada una de estas se basa en una condición media y un value. Este atributo describe el tamaño con el que se va a mostrar al imagen en píxeles. Con la combinación de srcset, con descriptores, el navegador e capaz de elegir que imagen es la más adecuada para el dispositivo del usuario.
Formato de imágenes
Los navegadores son capaces de soportar una gran cantidad de formato de imágenes. Hay formatos modernos como WebP o AVIF que tienen una mejor compresión que otros, como podrían ser PNG o JPEG. Es importante tener en cuenta que tengan una mejor compresión que otros, por que gracias a esto nuestros navegadores van a descargar archivos con un tamaño mucho más reducido, permitiéndonos reducir el tiempo de descargar de recursos, consiguiendo con esto obtener un menor LCP.
Por ello, es importante elegir el formato correcto para nuestras imágenes.
Compresión
Existen dos tipos de compresión
- Lossy: Este tipo de compresión funciona reduciendo la calidad de la imagen mediante la quantization, y descartando información acerca de colores adicionales mediante el chroma subsampling. Este tipo de compresión es más efectiva en imágenes con una gran densidad y una gran cantidad de colores, como podría ser una foto. Esto es por que los artefactos producidos por la compresión lossy son mucho más difíciles de detectar en imágenes tan detalladas. En cambio, en imágenes que cuentan con una gama más baja de colores, con elementos más similares o con filos más pronunciados es mucho menos efectiva. Este tipo de compresión puede ser utilizada en formatos JPEG, WebP y AVIF.
- Lossless: Este tipo de compresión reduce el tamaño del archivo comprimiendo la imagen sin perder datos. Este tipo de compresión genera un pixel en base a la comparación con los que se encuentran cerca del mismo. Este tipo de compresión puede ser utilizada en formatos GIF, JPEG, WebP y AVIF.
Para comprimir imágenes, puedes usar herramientas como Squoosh, ImageOptim, algún servicio de compresión de imágenes o alguna librería como imagemin.
picture
El elemento en HTML proporciona mayor flexibilidad para especificar múltiples formatos de imagen, como AVIF y WebP, y permite retroceder a formatos más compatibles como JPEG si el navegador no soporta los modernos. El navegador selecciona el primer formato compatible especificado en los elementos , y el elemento <img> sirve como respaldo con atributos como alt, width y height.
Los atributos media, srcset y sizes en <source> permiten seleccionar imágenes basadas en las características del dispositivo, como la resolución de pantalla (DPR) y el ancho de la ventana gráfica (viewport). Esto ayuda a optimizar la calidad de la imagen y el rendimiento, evitando servir imágenes innecesariamente grandes en dispositivos con pantallas más pequeñas.
Sin embargo, usar muchas variantes de imágenes puede aumentar la complejidad del código y el costo del servidor, además de reducir la eficiencia del caché del navegador. Por tanto, es crucial encontrar un equilibrio adecuado entre la cantidad de variantes y el impacto en la experiencia del usuario.
En resumen, el uso correcto del elemento <picture> y las estrategias de selección de imágenes pueden mejorar significativamente la carga y la visualización de imágenes en diferentes dispositivos, pero requiere una gestión cuidadosa de las variantes y los formatos para mantener un buen rendimiento y eficiencia.
Lazy loading
Otra táctica que se puede utilizar es la del lazy loading, donde indicamos al navegador imágenes que vamos a querer que se carguen en el momento en el que aparezcan en el viewport. Esto se consigue mediante el atributo lazy, en el que le decimos al navegador que no haga una descarga de la imagen hasta que la misma esté cerca o en el viewport. Esto permite optimizar la descarga de recursos permitiendo al navegador priorizar los recursos que son necesarios para mostrar el contenido principal que ya se encuentra en el viewport.
Cargar imágenes de forma diferida con el atributo loading
El atributo <loading> puede añadirse a los elementos <img> para indicar a los navegadores cómo deben ser cargados:
- eager: informa al navegador que la imagen debe cargarse inmediatamente, incluso si está fuera de la vista inicial. Este es también el valor predeterminado para el atributo <loading>.
- lazy: retrasa la carga de una imagen hasta que esté a una cierta distancia del área visible del viewport. Esta distancia varía según el navegador, pero generalmente es lo suficientemente grande como para que la imagen se cargue a tiempo cuando el usuario se desplaza hacia ella.
Decoding
Este atributo permite al navegador como debería decodificar la imagen. Cuenta con varios valores, que serían los siguientes:
- async: Que indica al navegador que la imagen sea decodificada de manera asíncrona, permitiendo mejorar el tiempo
en el que se renderiza otro contenido. - sync: Que indica al navegador que la imagen debería ser mostrada al mismo tiempo que otro contenido.
- auto: Es el valor por defecto e indica al navegador que realice las acciones que sean mejores para el usuario.
Video
Además de las imágenes, los videos son otro tipo de medio comúnmente utilizado en las páginas web. Antes de abordar posibles optimizaciones, es esencial entender cómo funciona el elemento <video>.
Archivos de fuente de video
Los archivos de video que vemos en nuestro sistema, como .mp4 o .webm, se denominan contenedores. Estos contenedores
contienen uno o más flujos de datos (streams), normalmente uno de video y otro de audio.
La compresión de estos flujos se realiza por separado utilizando códecs, que vendrían a ser un software o hardware utilizado para comprimir y descomprimir archivos de audio y video. Los códecs permiten que los datos multimedia se almacenen y transmitan de manera eficiente, reduciendo el tamaño de los archivos sin perder (demasiada) calidad.
Comprender la diferencia entre contenedores y códecs es crucial, ya que nos permite optimizar la compresión de archivos de video y audio, utilizando menos ancho
de banda y mejorando el rendimiento del contenido cargado (evitando problemas como el Largest Contentful Paint, LCP).
Formatos múltiples
Cuando trabajamos con archivos de tipo media es necesario trabajar con varios formatos, para que en caso de que uno de los mismos falle, cuente con uno de emergencia o fallback, ya que no todos los navegadores soportan los nuevos formatos que van saliendo.
Atributo poster
El atributo poster en el elemento <video> de HTML especifica una imagen que se muestra mientras se está descargando el video o hasta que el usuario inicia la reproducción.
Tiene varias beneficios, como podría ser el proporcionar una imagen atractiva y relevante que puede captar la atención del usuario y dar una pista sobre el contenido del video o evitar que el usuario vea un cuadro vacío o el primer fotograma del video, que puede no ser representativo del contenido.
Autoplay
El atributo autoplay en el elemento <video> de HTML indica al navegador que debe iniciar la reproducción del video tan pronto como sea posible, sin requerir la interacción del usuario.
Este atributo se utiliza cuando un video debe ser reproducido automáticamente, ya sea como fondo de pantalla o sustitución a los GIFs.
La mayoría de GIFs suelen ser muy pesados, y estoy puede afectar al rendimiento de nuestra página, consumiendo una gran cantidad de la banda ancha de la que disponemos para descargar recursos, que podría ser utilizada mejor para otros elementos más críticos.
Se debería evitar este tipo de formato de imagen, y hacer uso de los elementos como <video> que son mucho más eficientes para este tipo de recurso.
El uso de este atributo cuenta con bastantes beneficios, como los comentados anteriormwente, pero también hay consideraciones a tener en cuenta, como que si por ejemplo el video contiene audio, que puede afectar a la experiencia de usuario si por ejemplo tiene el volumen alto, aunque para esto se puede hacer uso del atributo muted. Otro punto negativo es que puede afectar a tecnologías de asistencia como los lectores de pantalla.
Reproducción iniciada por el usuario
Normalmente el navegador empieza a descargar el video tan pronto como el parseador de HTML pasa por el mismo.
La reproducción de video iniciada por el usuario (user-initiated playback) se refiere a cuando los usuarios tienen que interactuar explícitamente con un video, como hacer clic en un botón de reproducción, para que el video comience a reproducirse. Este enfoque tiene varias ventajas en términos de rendimiento, accesibilidad y experiencia del usuario.
Para esto puedes hacer uso del atributo preload, para indicar como va a ser descargado el archivo según los siguientes valores:
- Con el preload a none informamos al navegador de que ningún contenido del video debería ser descargado de antemano.
- En caso de darle el valor metadata el navegador solo hará una petición para obtener los metadatos del video, como podría ser la duración del mismo u otro tipo de datos.
Contenido embedido
Dada la complejidad que pude existir al a hora de optimizar y mostrar un video de manera eficiente, se puede plantear la idea de hacer uso de recursos dedicados a la reproducción de videos, como podría ser Youtube. Este tipo de servicios se encarga de optimizar por su cuenta la entrega de los videos por ti.
Pero tiene sus puntos negativos en el rendimiento. Los videos embebidos a menudo implican la carga de recursos adicionales, como scripts, estilos y anuncios de terceros, lo que puede aumentar el tiempo de carga de la página. Los elementos embebidos pueden cargar automáticamente contenido multimedia, lo que consume ancho de banda y recursos del navegador. Los scripts de terceros necesarios para cargar videos embebidos pueden bloquear el renderizado de otros elementos de la página hasta que se descarguen y ejecuten.
Fuentes web
Las fuentes web son esenciales para la estética y la legibilidad de un sitio web, pero pueden tener un impacto significativo en el rendimiento si no se gestionan adecuadamente. Las fuentes web pueden afectar al rendimiento de la página tanto en el tiempo de carga como en el tiempo de renderizado. Archivos de web font que son bastante pesados afectan negativamente al RCP, mientras que un font display incorrecto puede afectar al CLS.
Las fuentes web se definen en las hojas de estilo usando la declaración @font-face, especificando la familia de fuentes y la URL del recurso. El navegador solo descarga la fuente cuando determina que es necesaria para el diseño de la página o se pueden especificar en el código HTML utilizando la etiqueta con el atributo rel="stylesheet"
para cargar una hoja de estilo CSS que define las fuentes.
Para acelerar la descarga de fuentes, se pueden usar directivas preload que solicitan las fuentes temprano en el proceso de carga de la página. Sin embargo, es necesario usar preload con moderación para no desviar el ancho de banda de otros recursos importantes. Además, es importante agregar el atributo crossorigin debido a que las fuentes son consideradas recursos CORS.
Otra opción es insertar declaraciones @font-face directamente en el <head> del HTML usando el elemento style. Esto permite al navegador descubrir y descargar las fuentes antes, sin tener que esperar la hoja de estilo externa. Esta técnica evita la descarga de fuentes no necesarias, optimizando así la carga de la página. Sin embargo, no se recomienda insertar los archivos de fuente en el CSS debido a la sobrecarga de la codificación base64.
La correcta gestión de fuentes web es crucial para el rendimiento y la experiencia del usuario en una página web. Utilizar preload y @font-face en línea puede mejorar significativamente los tiempos de carga y asegurar que las fuentes necesarias estén disponibles cuando se necesiten, evitando el «flash» de texto sin estilo y mejorando la apariencia y legibilidad del contenido desde el primer momento.
Descarga
Después de descubrir las fuentes web y asegurar que son necesarias para el diseño de la página actual, el navegador puede descargarlas. La cantidad de fuentes web, su codificación y su tamaño de archivo pueden afectar significativamente la rapidez con la que se descargan y se renderizan las fuentes web en el navegador.
Autoalojar tus fuentes web
Las fuentes web pueden ser servidas a través de servicios de terceros, como Google Fonts, o pueden ser autoalojadas en tu servidor de origen. Al usar un servicio de terceros, tu página web necesita abrir una conexión al dominio del proveedor antes de poder comenzar a descargar las fuentes necesarias. Esto puede retrasar el descubrimiento y la posterior descarga de las fuentes web.
Este inconveniente puede reducirse usando la indicación de recurso preconnect. Al usar preconnect, puedes decirle al navegador que abra una conexión al origen cruzado antes de lo que normalmente lo haría:
link rel="preconnect" href="https://fonts.googleapis.com"
link rel="preconnect" href="https://fonts.gstatic.com" crossorigin
Puedes eliminar la necesidad de una conexión de terceros autoalojando tus fuentes web. Autoalojar tus fuentes web significa almacenar las fuentes directamente en el servidor de tu sitio web en lugar de depender de servicios externos o CDN (Content Delivery Network) para proporcionar las fuentes.
Tiene sus ventajas, como que tienes un control completo sobre su disponibilidad y entrega, que no estás sujeto a cambios inesperados o problemas de terceros servicios externos o que autoalojar fuentes web es más rápido que descargarlas desde un origen cruzado, entre las muchas que hay.
Todas estas ventajas se consiguen a costa de asumir la responsabilidad de la administración y el mantenimiento de los archivos de fuentes.
Formato WOFF2
El formato de archivo WOFF2 (Web Open Font Format 2) ofrece varias ventajas en términos de rendimiento al utilizar fuentes web en un sitio. Disfruta amplio soporte en navegadores y la mejor compresión. WOFF2 utiliza una compresión más eficiente en comparación con otros formatos de fuentes web como TTF (TrueType Font) o EOT (Embedded OpenType). Esto resulta en archivos de fuentes más pequeños, lo que reduce el tiempo de transferencia y el consumo de ancho de banda al cargar una página web. Es compatible con la mayoría de los navegadores web modernos, lo que garantiza una amplia disponibilidad y soporte para las fuentes en este formato. Los navegadores que admiten WOFF2 pueden aprovechar su capacidad de compresión mejorada para cargar fuentes de manera más eficiente.
Subconjuntar fuentes web
Subconjuntar fuentes web es el proceso de seleccionar y servir solo los glifos (caracteres) necesarios de una fuente completa, en lugar de cargar toda la fuente. Las fuentes web suelen incluir una amplia gama de glifos diferentes, que son necesarios para representar la gran variedad de caracteres usados en diferentes idiomas. Si tu página sirve contenido en un solo idioma o usa un solo alfabeto, puedes reducir el tamaño de tus fuentes web a través del subconjunto. Esto se hace especificando un número o un rango de puntos de código Unicode.
Un subconjunto es un conjunto reducido de los glifos que estaban incluidos en el archivo de la fuente web original. Por ejemplo, en lugar de servir todos los glifos, tu página podría servir un subconjunto específico para cierto tipo de carácteres. Dependiendo del subconjunto necesario, eliminar glifos puede reducir significativamente el tamaño de un archivo de fuente.
Si has decidido autoalojar tus fuentes web, el siguiente paso es generar y alojar esos subconjuntos tú mismo usando herramientas como glyphhanger o subfont.
Renderizado de fuentes
Después de que el navegador haya descubierto y descargado la cuente, es cuando esta va a ser renderizada. Por defecto los navegadores bloquean el renderizado de cualquier texto que hace uso de una web font hasta que sea descargado, para así evitar cambios bruscos en la página en sí.
Aunque puedes modificar el comportamiento que va a tener el navegador con respecto al renderizado, mediante una propiedad de CSS, que sería font display. Esta propiedad puede tener los siguientes valores:
- swap: Con este valor, el navegador no bloquea el renderizado, y muestra el texto inmediatamente antes de que se descargue y se modifique la web font que debería tener el texto. Con esto consigues que el contenido se muestre
inmediatamente sin tener que esperar a la descarga. El problema que tiene el hacer eso de esto es lo comentado antes, se puede puede dar un cambio muy brusco en la web por el cambio de fuentes. Esto puede afectar al CLS. - fallback: Con este valor, el navegador bloquea el renderizado de una fuente y pone un texto por defecto un pequeño periodo de tiempo. El tiempo de bloqueo es bastante corto. Hacer uso de fallback puede ser una buena decisión si los usuarios disponen de una buena velocidad, el problema se puede dar cuando cuenta con redes más lentas.
- optional: Con este valor, solo se va a hacer uso de la web font si esta ha tardado menos de 100 milisegundos en descargarse. En caso de que tarde más, no se hará uso de la misma en la página, el navegador por detrás descargará la fuente y la guardará en caché, para que posteriormente, al navegar dentro de la página esta web font se muestre automáticamente, ya que la misma ya ha sido descargada.
Web workers
Los Web Workers son una característica poderosa de JavaScript que permite ejecutar scripts en segundo plano, en hilos separados del hilo principal de la página web. Esto ayuda a optimizar el rendimiento de las aplicaciones web de varias maneras.
¿Qué son los Web Workers?
Los Web Workers permiten a los desarrolladores ejecutar scripts en un hilo separado del hilo principal del navegador. Esto significa que tareas intensivas en computación pueden ejecutarse sin bloquear la interfaz de usuario (UI), manteniendo la página web responsiva.
Beneficios de los Web Workers
- Mejora del Rendimiento y la Receptividad: Al mover las tareas complejas y de larga duración (como cálculos matemáticos intensivos, procesamiento de grandes conjuntos de datos, etc.) a un Web Worker, la UI permanece receptiva y el usuario no experimenta bloqueos o congelamientos.
- Separación de Tareas: Permite una mejor organización del código al separar las tareas que pueden ejecutarse de forma paralela. Esto hace que el desarrollo y mantenimiento del código sea más fácil y estructurado.
- Mejor Uso de Recursos del Sistema: Al utilizar múltiples núcleos del procesador, los Web Workers pueden mejorar el rendimiento de las aplicaciones al aprovechar mejor los recursos del sistema.
Cómo funcionan los Web Workers
Un Web Worker se crea utilizando la interfaz Worker
de JavaScript. Aquí hay un ejemplo básico:
const worker = new Worker('worker.js');
worker.postMessage('start');
worker.onmessage = function(event) {
console.log('Mensaje del Worker:', event.data);
};
Y en el archivo worker.js
:
self.onmessage = function(event) {
if (event.data === 'start') {
// Realizar una tarea intensiva en computación
let result = 0;
for (let i = 0; i < 1e7; i++) {
result += i;
}
self.postMessage(result);
}
};
Limitaciones de los Web Workers
- No Acceso al DOM: Los Web Workers no tienen acceso directo al DOM. Esto significa que no pueden manipular la interfaz de usuario directamente. Solo pueden comunicarse con el hilo principal a través de mensajes.
- Sobrecarga de Comunicación: La comunicación entre el hilo principal y los Web Workers implica la serialización y deserialización de mensajes, lo cual puede ser una sobrecarga si se hace en exceso.
- Compatibilidad del Navegador: Aunque la mayoría de los navegadores modernos soportan Web Workers, siempre es buena práctica verificar la compatibilidad si se desea soportar navegadores más antiguos.
Uso de Web Workers para optimización del Rendimiento
- Procesamiento de Datos: Web Workers son ideales para el procesamiento de grandes conjuntos de datos, como la manipulación de arrays grandes, procesamiento de imágenes, o cálculos matemáticos complejos.
- Operaciones de Entrada/Salida: Las operaciones que no afectan directamente al DOM, como la comunicación con un servidor, el procesamiento de archivos y la decodificación de medios, pueden beneficiarse enormemente del uso de Web Workers.
- Aplicaciones en Tiempo Real: En aplicaciones que requieren cálculos en tiempo real, como juegos en línea o aplicaciones gráficas, los Web Workers pueden ayudar a mantener la fluidez y la respuesta rápida de la interfaz de usuario.
En resumen, los Web Workers son una herramienta esencial para optimizar el rendimiento de las aplicaciones web al permitir la ejecución de tareas en segundo plano, manteniendo la interfaz de usuario responsiva y mejorando la eficiencia general del uso de recursos del sistema.
Conclusiones
En este tutorial hemos abordado una gran parte de las diferentes técnicas que existen de como optimizar el rendimiento en nuestros sitios web, explicando como funcionan por detrás algunos de ellos y el proceso que siguen. A pesar de que hoy en día hay muchos Frameworks, Bundlers u otros elementos que nos realizan estas optimizaciones por detrás, es interesante conocer las opciones de las que disponemos y que hacen estas herramientas por detrás.