Analiza el rendimiento de la ruta de representación crítica

Identificar y resolver los cuellos de botella en el rendimiento de la ruta de renderización crítica requiere un buen conocimiento de los errores comunes. Hagamos un recorrido práctico para extraer los patrones comunes de rendimiento que lo ayudarán a optimizar sus páginas.

Optimizar la ruta de renderización crítica permite que el navegador muestre la página lo más rápido posible: las páginas más rápidas generan una mayor participación, más vistas de páginas y una mejora en las conversiones. Para minimizar la cantidad de tiempo que un visitante pasa viendo una pantalla en blanco, debemos optimizar qué recursos se cargan y en qué orden.

Para ayudar a ilustrar este proceso, comencemos con el caso más simple posible y avancemos gradualmente en nuestra página para incluir recursos, estilos y lógica de aplicación adicionales. En el proceso, optimizaremos cada caso y también veremos qué problemas pueden salir mal.

Hasta ahora, nos hemos enfocado exclusivamente en lo que sucede en el navegador una vez que el recurso (archivo CSS, JS o HTML) está disponible para su procesamiento. Pasamos por alto el tiempo que lleva recuperar el recurso de la caché o de la red. Supongamos lo siguiente:

  • Un recorrido completo desde la red (latencia de propagación) hasta el servidor cuesta 100 ms.
  • El tiempo de respuesta del servidor es de 100 ms para el documento HTML y 10 ms para el resto de los archivos.

La experiencia Hello World

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

Probar

Comenzaremos con el lenguaje de marcado HTML básico y una sola imagen, sin CSS ni JavaScript. Abriremos la línea de tiempo de la red en las Herramientas para desarrolladores de Chrome y, luego, inspeccionaremos la cascada de recursos resultante:

CRP

Según lo previsto, el archivo HTML tardó aproximadamente 200 ms en descargarse. Ten en cuenta que la parte transparente de la línea azul representa el tiempo que el navegador espera en la red sin recibir bytes de respuesta, mientras que la parte sólida muestra el tiempo necesario para finalizar la descarga después de que se hayan recibido los primeros bytes de respuesta. La descarga de HTML es muy pequeña (<4K), por lo que solo necesitamos un recorrido para recuperar el archivo completo. Como resultado, el documento HTML tarda alrededor de 200 ms en recuperarse: la mitad de ese tiempo se dedicó a esperar en la red y la otra mitad a la respuesta del servidor.

Cuando el contenido HTML está disponible, el navegador analiza los bytes, los convierte en tokens y crea el árbol del DOM. Ten en cuenta que Herramientas para desarrolladores informa de manera práctica el tiempo del evento DOMContentLoaded en la parte inferior (216 ms), que también corresponde a la línea vertical azul. La brecha entre el final de la descarga HTML y la línea vertical azul (DOMContentLoaded) es el tiempo que le lleva al navegador compilar el árbol del DOM; en este caso, solo unos milisegundos.

Observa que nuestra "foto increíble" no bloqueó el evento domContentLoaded. Al parecer, podemos construir el árbol de renderización y hasta pintar la página sin esperar a cada elemento de la página: no todos los recursos son fundamentales para proporcionar la primera pintura rápida. De hecho, cuando hablamos de la ruta de acceso de representación crítica, por lo general, nos referimos al lenguaje de marcado HTML, CSS y JavaScript. Las imágenes no bloquean la representación inicial de la página, aunque también debemos tratar de pintar las imágenes lo antes posible.

Dicho esto, el evento load (también conocido como onload) está bloqueado en la imagen: Herramientas para desarrolladores informa el evento onload a los 335 ms. Recuerda que el evento onload marca el punto en el que se descargaron y procesaron todos los recursos que requiere la página. En este punto, el ícono giratorio de carga puede dejar de girar en el navegador (la línea vertical roja en la cascada).

Cómo agregar JavaScript y CSS a la combinación

Nuestra página "Experiencia de Hello World" parece simple, pero muchas cosas suceden en segundo plano. En la práctica, necesitaremos más que solo HTML: es probable que haya una hoja de estilo CSS y una o más secuencias de comandos para agregar interactividad a nuestra página. Agreguemos ambos a la mezcla y veamos qué sucede:

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Script</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="timing.js"></script>
  </body>
</html>

Probar

Antes de agregar JavaScript y CSS:

Ruta de acceso de representación crítica de DOM

Con JavaScript y CSS:

DOM, CSSOM y JS

La adición de archivos CSS y JavaScript externos suma a nuestra cascada dos solicitudes adicionales que el navegador envía casi al mismo tiempo. Sin embargo, ten en cuenta que ahora la diferencia de tiempo entre los eventos domContentLoaded y onload es mucho menor.

¿Qué pasó?

  • A diferencia de nuestro ejemplo de HTML simple, también necesitamos obtener y analizar el archivo CSS para construir el CSSOM, y necesitamos el DOM y el CSSOM para compilar el árbol de representación.
  • Dado que la página también contiene un archivo JavaScript que bloquea el analizador, el evento domContentLoaded se bloquea hasta que se descargue y analice el archivo CSS: Debido a que JavaScript podría consultar el CSSOM, debemos bloquear el archivo CSS hasta que se descargue para poder ejecutar JavaScript.

¿Qué sucede si reemplazamos nuestra secuencia de comandos externa por una secuencia de comandos intercalada? Incluso si la secuencia de comandos está directamente integrada en la página, el navegador no podrá ejecutarla hasta que se construya el CSSOM. En resumen, el código JavaScript integrado también bloquea el analizador.

Dicho esto, a pesar del bloqueo del CSS, ¿la integración de la secuencia de comandos hace que la página se represente más rápido? Intentémoslo y veamos qué sucede.

JavaScript externo:

DOM, CSSOM y JS

JavaScript integrado:

DOM, CSSOM y JS integrado

Haremos una solicitud menos, pero los tiempos de onload y domContentLoaded son los mismos. ¿Por qué? Bueno, sabemos que no importa si el código JavaScript está integrado o es externo porque, apenas el navegador alcanza la etiqueta de la secuencia de comandos, se bloquea y espera hasta que se construya el CSSOM. Además, en nuestro primer ejemplo, el navegador descarga CSS y JavaScript en paralelo, y estos sistemas terminan de descargarse aproximadamente al mismo tiempo. En este caso, intercalar el código JavaScript no nos ayuda mucho. Sin embargo, existen varias estrategias que pueden lograr que nuestra página se renderice más rápido.

En primer lugar, recuerda que todas las secuencias de comandos intercaladas bloquean el analizador, pero para secuencias de comandos externas podemos agregar la palabra clave “async” para desbloquear el analizador. Deshagamos la intercalación y probemos eso:

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Async</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script async src="timing.js"></script>
  </body>
</html>

Probar

JavaScript que bloquea el analizador (externo):

DOM, CSSOM y JS

JavaScript asíncrono (externo):

DOM, CSSOM, JS asíncrono

¡Mucho mejor! El evento domContentLoaded se activa poco después del análisis del HTML; el navegador sabe que no debe bloquearse en JavaScript y, debido a que no hay otro analizador que bloquee las secuencias de comandos, la construcción del CSSOM también puede continuar en paralelo.

Como alternativa, podríamos haber integrado tanto el CSS como JavaScript:

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Inlined</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <style>
      p {
        font-weight: bold;
      }
      span {
        color: red;
      }
      p span {
        display: none;
      }
      img {
        float: right;
      }
    </style>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline'; // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>

Probar

DOM, CSS intercalado, JS integrado

Ten en cuenta que el tiempo de domContentLoaded es el mismo que en el ejemplo anterior. En lugar de marcar nuestro JavaScript como asíncrono, integramos CSS y JS a la página. Esto agranda mucho nuestra página HTML, pero la ventaja es que el navegador no tiene que esperar para obtener recursos externos, ya que todo está ahí mismo en la página.

Como puedes ver, incluso con una página muy simple, optimizar la ruta de acceso de representación crítica es un ejercicio no trivial: necesitamos comprender el gráfico de dependencia entre diferentes recursos, identificar qué recursos son "críticos" y elegir entre diferentes estrategias para incluir esos recursos en la página. No hay una única solución a este problema: cada página es diferente. Debes seguir un proceso similar por tu cuenta para descubrir la mejor estrategia.

Dicho esto, veamos si podemos retroceder e identificar algunos patrones generales de rendimiento.

Patrones de rendimiento

La página más simple posible consiste solo en el lenguaje de marcado HTML, sin CSS, JavaScript ni otros tipos de recursos. Para representar esta página, el navegador debe iniciar la solicitud, esperar a que llegue el documento HTML, analizarlo, compilar el DOM y, por último, renderizarlo en la pantalla:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

Probar

CRP de Hello World

El tiempo entre T0 y T1 captura los tiempos de procesamiento de la red y del servidor. En el mejor de los casos (si el archivo HTML es pequeño), solo un recorrido de la red recupera todo el documento. Debido a la forma en que el TCP transporta los protocolos, los archivos más grandes pueden requerir más recorridos. Como resultado, en el mejor de los casos, la página anterior tiene una ruta de acceso de renderización crítica de un solo recorrido (mínima).

Ahora, consideremos la misma página, pero con un archivo CSS externo:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

Probar

CRP de DOM + CSSOM

Una vez más, se genera un recorrido de la red para obtener el documento HTML y, luego, el lenguaje de marcado recuperado nos indica que también necesitamos el archivo CSS; esto significa que el navegador debe regresar al servidor y obtener el CSS para poder representar la página en la pantalla. Como resultado, esta página realiza un mínimo de dos recorridos antes de que se pueda mostrar. Una vez más, el archivo CSS puede requerir varios recorridos, por lo que ponemos énfasis en “como mínimo”.

Definamos el vocabulario que usamos para describir la ruta de acceso de representación crítica:

  • Recurso crítico: Es un recurso que podría bloquear la renderización inicial de la página.
  • Ruta de interacciones crítica: Cantidad de recorridos o el tiempo total necesario para recuperar todos los recursos críticos.
  • Bytes críticos: es la cantidad total de bytes necesarios para obtener la primera renderización de la página, que es la suma de los tamaños de archivo de transferencia de todos los recursos críticos. Nuestro primer ejemplo, con una sola página HTML, contenía un único recurso crítico (el documento HTML); la longitud de la ruta de acceso crítica también era igual a un recorrido de la red (suponiendo que el archivo fuera pequeño) y el total de bytes críticos era solo el tamaño de transferencia del documento HTML en sí.

Ahora comparemos esto con las características de la ruta crítica del ejemplo de HTML + CSS anterior:

CRP de DOM + CSSOM

  • 2 recursos críticos
  • 2 o más recorridos para la longitud mínima de la ruta crítica
  • 9 KB de bytes críticos

Necesitamos HTML y CSS para construir el árbol de representación. En consecuencia, tanto HTML como CSS son recursos críticos: el CSS se obtiene solo después de que el navegador obtiene el documento HTML, por lo que la longitud de la ruta de acceso crítica es, como mínimo, de dos recorridos. Ambos recursos suman un total de 9 KB de bytes críticos.

Ahora, agreguemos un archivo JavaScript adicional a la mezcla.

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js"></script>
  </body>
</html>

Probar

Agregamos app.js, que es un recurso externo de JavaScript en la página y un recurso que bloquea el analizador (es decir, crítico). Peor aún, para ejecutar el archivo JavaScript, tenemos que bloquear y esperar el CSSOM. Recuerda que JavaScript puede consultar el CSSOM y, por lo tanto, el navegador se detiene hasta que se descargue style.css y se construya el CSSOM.

Ruta de acceso de representación crítica de DOM, CSSOM, JavaScript

Dicho esto, en la práctica, si observamos la "cascada de red" de esta página, notarás que las solicitudes de CSS y JavaScript se inician aproximadamente al mismo tiempo: el navegador obtiene el HTML, descubre ambos recursos e inicia ambas solicitudes. Como resultado, la página anterior tiene las siguientes características de ruta crítica:

  • 3 recursos críticos
  • 2 o más recorridos para la longitud mínima de la ruta crítica
  • 11 KB de bytes críticos

Ahora tenemos tres recursos críticos que suman 11 KB de bytes críticos, pero la longitud de nuestra ruta de acceso crítica sigue siendo de dos recorridos, ya que podemos transferir CSS y JavaScript en paralelo. Determinar las características de tu ruta de renderización crítica implica que podrás identificar los recursos críticos y comprender cómo el navegador programará la recuperación de datos. Continuemos con nuestro ejemplo.

Después de chatear con nuestros desarrolladores del sitio, nos dimos cuenta de que no es necesario que el código JavaScript que incluimos en nuestra página sea bloqueador, ya que tenemos algunas estadísticas y otro código que no necesita bloquear la representación de nuestra página. Con ese conocimiento, podemos agregar el atributo "async" a la etiqueta de la secuencia de comandos para desbloquear el analizador:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

Probar

CRP de DOM, CSSOM, JavaScript asíncrona

Una secuencia de comandos asíncrona tiene varias ventajas:

  • La secuencia de comandos ya no bloquea el analizador y no forma parte de la ruta de acceso de renderización crítica.
  • Debido a que no hay otras secuencias de comandos críticas, el CSS no necesita bloquear el evento domContentLoaded.
  • Cuanto antes se active el evento domContentLoaded, antes podrá comenzar a ejecutarse otra lógica de la app.

Como resultado, nuestra página optimizada volvió a tener dos recursos críticos (HTML y CSS), con una longitud mínima de ruta de acceso crítica de dos recorridos y un total de 9 KB de bytes críticos.

Por último, si la hoja de estilo CSS solo se necesitara para impresiones, ¿cómo se vería?

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" media="print" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

Probar

CRP de DOM, CSS sin bloqueo y JavaScript asíncrono

Como el recurso style.css solo se usa para imprimir, el navegador no necesita aplicar un bloqueo para renderizar la página. Por lo tanto, tan pronto como se complete la construcción del DOM, el navegador tendrá suficiente información para representar la página. Como resultado, esta página tiene un solo recurso crítico (el documento HTML) y la longitud mínima de la ruta de acceso de representación crítica es de un recorrido.

Comentarios