Intercalado de recursos en frameworks de JavaScript

Mejora del Largest Contentful Paint en el ecosistema de JavaScript

Como parte del proyecto Aurora, Google ha estado trabajando con frameworks web populares para garantizar que funcionen bien de acuerdo con las Métricas web esenciales. Angular y Next.js ya se integraron las fuentes, lo que se explica en la primera parte de este artículo. La segunda optimización que abordaremos es el intercalado fundamental de CSS, que ahora está habilitado de forma predeterminada en la CLI de Angular y tiene una implementación en curso en Nuxt.js.

Incrustación de fuentes

Después de analizar cientos de aplicaciones, el equipo de Aurora descubrió que los desarrolladores suelen incluir fuentes en sus aplicaciones haciendo referencia a ellas en el elemento <head> de index.html. A continuación, se muestra un ejemplo de cómo se vería esto cuando se incluyan íconos de material:

<!doctype html>
<html lang="en">
<head>
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  ...
</html>

Aunque este patrón es completamente válido y funcional, bloquea la renderización de la aplicación y presenta una solicitud adicional. Para comprender mejor lo que sucede, consulta el código fuente de la hoja de estilo a la que se hace referencia en el HTML anterior:

/* fallback */
@font-face {
  font-family: 'Material Icons';
  font-style: normal;
  font-weight: 400;
  src: url(https://fonts.gstatic.com/font.woff2) format('woff2');
}

.material-icons {
  /*...*/
}

Observa cómo la definición font-face hace referencia a un archivo externo alojado en fonts.gstatic.com. Cuando se carga la aplicación, el navegador primero debe descargar la hoja de estilo original a la que se hace referencia en el encabezado.

Una imagen que muestra cómo el sitio web debe realizar una solicitud al servidor y descargar la hoja de estilo externa
Primero, el sitio web carga la hoja de estilo de la fuente.

A continuación, el navegador descarga el archivo woff2 y, por último, puede continuar con el procesamiento de la aplicación.

Una imagen que muestra las dos solicitudes realizadas, una para la hoja de estilo de fuente y la segunda para el archivo de fuente.
A continuación, se realiza una solicitud para cargar la fuente.

Una oportunidad de optimización es descargar la hoja de estilo inicial durante el tiempo de compilación y, luego, intercalarla en index.html. De esta forma, se omite todo un recorrido de ida y vuelta a la CDN durante el tiempo de ejecución, lo que reduce el tiempo de bloqueo.

Cuando se compila la aplicación, se envía una solicitud a la CDN, la cual recupera la hoja de estilo, la intercala en el archivo HTML y se agrega un <link rel=preconnect> al dominio. Si aplicamos esta técnica, obtendríamos el siguiente resultado:

<!doctype html>
<html lang="en">
<head>
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin >
  <style type="text/css">
  @font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/font.woff2) format('woff2');}.material-icons{/*...*/}</style>
  ...
</html>

La incorporación de fuentes ahora está disponible en Next.js y Angular

Cuando los desarrolladores de frameworks implementan la optimización en las herramientas subyacentes, facilitan que las aplicaciones existentes y nuevas la habiliten, lo que aporta la mejora a todo el ecosistema.

Esta mejora está habilitada de forma predeterminada desde Next.js v10.2 y Angular v11. Ambas son compatibles con la integración de fuentes de Google y Adobe. Angular espera incorporar esta última en la versión 12.2.

Puedes encontrar la implementación de la inserción de fuentes en Next.js en GitHub y consulta el video en el que se explica esta optimización en el contexto de Angular.

Incorporar CSS crítico

Otra mejora implica perfeccionar las métricas First Contentful Paint (FCP) y Largest Contentful Paint (LCP). Para ello, intercala CSS críticas. El CSS crítico de una página incluye todos los diseños que se usaron en su renderización inicial. Para obtener más información sobre el tema, consulta Aplaza el CSS que no sea crítico.

Notamos que muchas aplicaciones cargan diseños de forma síncrona, lo que bloquea su renderización. Una solución rápida es cargar los estilos de forma asíncrona. En lugar de cargar las secuencias de comandos con media="all", configura el valor del atributo media como print y, una vez que se complete la carga, reemplaza el valor del atributo a all:

<link rel="stylesheet" href="..." media="print" onload="this.media='all'">

Sin embargo, esta práctica puede causar que el contenido sin estilo parpadee.

La página parece parpadear a medida que se cargan los diseños.

En el video anterior, se muestra cómo se renderiza una página, que carga sus estilos de forma asíncrona. Este parpadeo se produce porque el navegador primero comienza a descargar los diseños y, luego, renderiza el código HTML que sigue. Una vez que el navegador descarga los estilos, activa el evento onload del elemento de vínculo, actualiza el atributo media a all y aplica los estilos al DOM.

Durante el tiempo que transcurre entre la renderización del código HTML y la aplicación de los estilos, se quita parcialmente el estilo de la página. Cuando el navegador usa los estilos, vemos un parpadeo, lo que constituye una mala experiencia del usuario y genera regresiones en el Cambio de diseño acumulado (CLS).

La incorporación crítica de CSS, junto con la carga de diseño asíncrona, pueden mejorar el comportamiento de carga. Para encontrar los estilos que se usan en la página, la herramienta critters observa los selectores de una hoja de estilo y los hace coincidir con el código HTML. Cuando encuentra una coincidencia, considera los diseños correspondientes como parte del CSS fundamental y los intercala.

Veamos un ejemplo:

Qué no debes hacer
<head>
   <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">
</head>
<body>
  <section>
    <button class="primary"></button>
  </section>
</body>
/* styles.css */
section button.primary {
  /* ... */
}
.list {
  /* ... */
}

Ejemplo antes de la incorporación.

En el ejemplo anterior, las criaturas leerán y analizarán el contenido de styles.css. Luego, hace coincidir los dos selectores con el HTML y descubre que usamos section button.primary. Por último, las criaturas intercalarán los estilos correspondientes en el elemento <head> de la página, lo que generará lo siguiente:

<head>
  <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">
  <style>
  section button.primary {
    /* ... */
  }
  </style>
</head>
<body>
  <section>
    <button class="primary"></button>
  </section>
</body>

Ejemplo después de la incorporación.

Después de intercalar el CSS crítico en el HTML, verás que el parpadeo de la página desapareció:

La página se carga después de la integración de CSS.

La incorporación crítica de CSS ahora está disponible en Angular y se habilita de forma predeterminada en la versión 12. Si usas la versión 11, establece la propiedad inlineCritical en true en angular.json para activarla. Para habilitar esta función en Next.js, agrega experimental: { optimizeCss: true } a tu next.config.js.

Conclusiones

En esta publicación, analizamos parte de la colaboración entre los frameworks de Chrome y la Web. Si eres el autor de un framework y reconoces algunos de los problemas que abordamos con tu tecnología, esperamos que nuestros hallazgos te inspiren a aplicar optimizaciones de rendimiento similares.

Obtén más información sobre las mejoras. Puedes encontrar una lista completa del trabajo de optimización que estuvimos haciendo para las Métricas web esenciales en la publicación Introducción a Aurora.