Optimiza la carga de recursos

En el módulo anterior, se exploró algo de la teoría detrás de la ruta de renderización crítica y cómo los recursos que bloquean la renderización y el análisis pueden retrasar la renderización inicial de una página. Ahora que comprendes parte de la teoría detrás de esto, puedes aprender algunas técnicas para optimizar la ruta de renderización crítica.

A medida que se carga una página, se hace referencia a muchos recursos dentro de su código HTML que le proporcionan su apariencia y diseño a través de CSS, así como su interactividad a través de JavaScript. En este módulo, se abordan varios conceptos importantes relacionados con estos recursos y cómo afectan el tiempo de carga de una página.

Bloqueo de renderización

Como se analizó en el módulo anterior, CSS es un recurso que bloquea el procesamiento, ya que impide que el navegador procese cualquier contenido hasta que se construya el Modelo de objetos CSS (CSSOM). El navegador bloquea la renderización para evitar un destello de contenido sin diseño (FOUC), que no es deseable desde el punto de vista de la experiencia del usuario.

En el video anterior, se muestra un breve FOUC en el que se puede ver la página sin ningún diseño. Posteriormente, se aplican todos los estilos una vez que se termina de cargar la CSS de la página desde la red, y la versión sin estilo de la página se reemplaza de inmediato por la versión con estilo.

En general, un FOUC es algo que no sueles ver, pero el concepto es importante para que sepas por qué el navegador bloquea la renderización de la página hasta que se descarga y aplica el CSS a la página. El bloqueo de la renderización no es necesariamente indeseable, pero debes minimizar su duración manteniendo tu CSS optimizado.

Bloqueo del analizador

Un recurso que bloquea el análisis interrumpe el analizador de HTML, como un elemento <script> sin atributos async o defer. Cuando el analizador encuentra un elemento <script>, el navegador debe evaluar y ejecutar la secuencia de comandos antes de continuar con el análisis del resto del código HTML. Esto se diseñó de esta manera, ya que las secuencias de comandos pueden modificar el DOM o acceder a él durante un tiempo mientras aún se está construyendo.

<!-- This is a parser-blocking script: -->
<script src="/script.js"></script>

Cuando se usan archivos JavaScript externos (sin async o defer), el analizador se bloquea desde el momento en que se descubre el archivo hasta que se descarga, analiza y ejecuta. Cuando se usa JavaScript intercalado, el analizador también se bloquea hasta que se analiza y ejecuta la secuencia de comandos intercalada.

El escáner de precarga

El analizador de carga previa es una optimización del navegador en forma de un analizador de HTML secundario que analiza la respuesta HTML sin procesar para encontrar y recuperar de forma especulativa recursos antes de que el analizador de HTML principal los descubra. Por ejemplo, el escáner de precarga permitiría que el navegador comience a descargar un recurso especificado en un elemento <img>, incluso cuando el analizador de HTML esté bloqueado mientras recupera y procesa recursos como CSS y JavaScript.

Para aprovechar el escáner de carga previa, los recursos críticos deben incluirse en el lenguaje de marcado HTML que envía el servidor. El analizador de carga previa no puede detectar los siguientes patrones de carga de recursos:

  • Imágenes cargadas por CSS con la propiedad background-image. Estas referencias de imágenes están en CSS y el escáner de carga previa no las puede detectar.
  • Son secuencias de comandos cargadas de forma dinámica en forma de marcado de elementos <script> insertado en el DOM con JavaScript o módulos cargados con import() dinámico.
  • Es el código HTML renderizado en el cliente con JavaScript. Este tipo de marcado se encuentra dentro de cadenas en recursos de JavaScript y el escáner de carga previa no lo puede detectar.
  • Declaraciones de @import de CSS

Estos patrones de carga de recursos son todos recursos descubiertos tarde y, por lo tanto, no se benefician del análisis previo a la carga. Evítalos siempre que sea posible. Sin embargo, si no es posible evitar esos patrones, puedes usar una sugerencia de preload para evitar demoras en el descubrimiento de recursos.

CSS

El CSS determina la presentación y el diseño de una página. Como se describió anteriormente, CSS es un recurso que bloquea el procesamiento, por lo que optimizar tu CSS podría tener un impacto considerable en el tiempo de carga general de la página.

Reducción

Reducir los archivos CSS disminuye el tamaño de un recurso CSS, lo que hace que se descarguen más rápido. Esto se logra principalmente quitando contenido de un archivo CSS fuente, como espacios y otros caracteres invisibles, y generando el resultado en un archivo recién optimizado:

/* Unminified CSS: */

/* Heading 1 */
h1 {
  font-size: 2em;
  color: #000000;
}

/* Heading 2 */
h2 {
  font-size: 1.5em;
  color: #000000;
}
/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}

En su forma más básica, la reducción de CSS es una optimización eficaz que podría mejorar el FCP de tu sitio web y, tal vez, incluso el LCP en algunos casos. Las herramientas como los bundlers pueden realizar esta optimización automáticamente en las compilaciones de producción.

Quita el código CSS sin usar

Antes de renderizar cualquier contenido, el navegador debe descargar y analizar todas las hojas de estilo. El tiempo necesario para completar el análisis también incluye los estilos que no se usan en la página actual. Si usas un agrupador que combina todos los recursos de CSS en un solo archivo, es probable que tus usuarios descarguen más CSS del que se necesita para renderizar la página actual.

Para descubrir el CSS que no se usa en la página actual, usa la herramienta Coverage en las Herramientas para desarrolladores de Chrome.

Captura de pantalla de la herramienta de cobertura en las Herramientas para desarrolladores de Chrome. Se selecciona un archivo CSS en su panel inferior, que muestra una cantidad considerable de CSS no utilizados por el diseño de página actual.
La herramienta de cobertura de las Herramientas para desarrolladores de Chrome es útil para detectar el CSS (y el JavaScript) que no usa la página actual. Se puede usar para dividir archivos CSS en varios recursos que se cargarán en diferentes páginas, en lugar de enviar un paquete CSS mucho más grande que puede retrasar el procesamiento de la página.

Quitar el CSS sin usar tiene un doble efecto: además de reducir el tiempo de descarga, optimizas la construcción del árbol de renderización, ya que el navegador necesita procesar menos reglas de CSS.

Evita las declaraciones @import de CSS

Si bien puede parecer conveniente, debes evitar las declaraciones @import en CSS:

/* Don't do this: */
@import url('style.css');

De manera similar a cómo funciona el elemento <link> en HTML, la declaración @import en CSS te permite importar un recurso CSS externo desde una hoja de estilo. La principal diferencia entre estos dos enfoques es que el elemento <link> de HTML forma parte de la respuesta HTML y, por lo tanto, se descubre mucho antes que un archivo CSS descargado por una declaración @import.

El motivo es que, para que se descubra una declaración @import, primero se debe descargar el archivo CSS que la contiene. Esto genera lo que se conoce como una cadena de solicitudes que, en el caso de CSS, retrasa el tiempo que tarda una página en renderizarse inicialmente. Otro inconveniente es que el escáner de carga previa no puede detectar las hojas de estilo cargadas con una declaración @import y, por lo tanto, se convierten en recursos que bloquean la renderización y se detectan tarde.

<!-- Do this instead: -->
<link rel="stylesheet" href="style.css">

En la mayoría de los casos, puedes reemplazar el @import con un elemento <link rel="stylesheet">. Los elementos <link> permiten que las hojas de estilo se descarguen de forma simultánea y reducen el tiempo de carga general, a diferencia de las declaraciones @import, que descargan las hojas de estilo de forma consecutiva.

Intercala el CSS crítico

El tiempo que lleva descargar archivos CSS puede aumentar el FCP de una página. La inserción de estilos críticos en el documento <head> elimina la solicitud de red para un recurso CSS y, cuando se realiza correctamente, puede mejorar los tiempos de carga iniciales cuando la caché del navegador de un usuario no está preparada. El resto del CSS se puede cargar de forma asíncrona o se puede agregar al final del elemento <body>.

<head>
  <title>Page Title</title>
  <!-- ... -->
  <style>h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}</style>
</head>
<body>
  <!-- Other page markup... -->
  <link rel="stylesheet" href="non-critical.css">
</body>

Por otro lado, insertar una gran cantidad de CSS agrega más bytes a la respuesta HTML inicial. Dado que los recursos HTML a menudo no se pueden almacenar en caché durante mucho tiempo (o no se pueden almacenar en caché en absoluto), esto significa que el CSS intercalado no se almacena en caché para las páginas posteriores que pueden usar el mismo CSS en hojas de estilo externas. Prueba y mide el rendimiento de tu página para asegurarte de que las compensaciones valgan la pena.

Demostraciones de CSS

JavaScript

JavaScript impulsa la mayor parte de la interactividad en la Web, pero tiene un costo. Enviar demasiado JavaScript puede hacer que tu página web tarde en responder durante la carga de la página y hasta puede causar problemas de capacidad de respuesta que ralenticen las interacciones, lo que puede ser frustrante para los usuarios.

JavaScript que bloquea el procesamiento

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 la secuencia de comandos. Del mismo modo, las secuencias de comandos intercaladas bloquean el analizador hasta que se analizan y ejecutan.

async en comparación con defer

async y defer permiten que se carguen secuencias de comandos externas sin bloquear el analizador de HTML, mientras que las secuencias de comandos (incluidas las intercaladas) con type="module" se aplazan automáticamente. Sin embargo, async y defer tienen algunas diferencias que es importante comprender.

Representación de varios mecanismos de carga de secuencias de comandos, todos con detalles sobre los roles de análisis, recuperación y ejecución según los distintos atributos utilizados, como async, defer, type=&quot;module&quot; y una combinación de los tres.
Fuente: https://html.spec.whatwg.org/multipage/scripting.html

Las secuencias de comandos cargadas con async se analizan y ejecutan inmediatamente después de descargarse, mientras que las secuencias de comandos cargadas con defer se ejecutan cuando finaliza el análisis del documento HTML, lo que ocurre al mismo tiempo que el evento DOMContentLoaded del navegador. Además, las secuencias de comandos de async se pueden ejecutar fuera de orden, mientras que las de defer se ejecutan en el orden en que aparecen en el lenguaje de marcado.

Procesamiento del cliente

En general, debes evitar usar JavaScript para renderizar contenido crítico o el elemento LCP de una página. Esto se conoce como renderización del cliente y es una técnica que se usa mucho en las aplicaciones de una sola página (SPA).

El lenguaje de marcado renderizado por JavaScript evita el escáner de precarga, ya que los recursos incluidos en el lenguaje de marcado renderizado por el cliente no se pueden detectar. Esto podría retrasar la descarga de recursos cruciales, como una imagen de LCP. El navegador solo comienza a descargar la imagen de LCP después de que se ejecuta la secuencia de comandos y se agrega el elemento al DOM. A su vez, la secuencia de comandos solo se puede ejecutar después de que se haya descubierto, descargado y analizado. Esto se conoce como una cadena de solicitudes críticas y se debe evitar.

Además, es más probable que la renderización del lenguaje de marcado con JavaScript genere tareas largas que el lenguaje de marcado descargado del servidor en respuesta a una solicitud de navegación. El uso extensivo de la renderización del HTML del cliente puede afectar negativamente la latencia de interacción. Esto es especialmente cierto en los casos en los que el DOM de una página es muy grande, lo que activa un trabajo de renderización significativo cuando JavaScript modifica el DOM.

Reducción

Al igual que con CSS, reducir el uso de JavaScript disminuye el tamaño de archivo de un recurso de secuencia de comandos. Esto puede acelerar las descargas, lo que permite que el navegador pase al proceso de análisis y compilación de JavaScript más rápidamente.

Además, la reducción de JavaScript va un paso más allá que la reducción de otros recursos, como CSS. Cuando se minimiza JavaScript, no solo se quitan elementos como espacios, tabulaciones y comentarios, sino que también se acortan los símbolos en el código fuente de JavaScript. Este proceso a veces se conoce como uglification. Para ver la diferencia, toma el siguiente código fuente de JavaScript:

// Unuglified JavaScript source code:
export function injectScript () {
  const scriptElement = document.createElement('script');
  scriptElement.src = '/js/scripts.js';
  scriptElement.type = 'module';

  document.body.appendChild(scriptElement);
}

Cuando se minimiza el código fuente de JavaScript anterior, el resultado puede parecerse al siguiente fragmento de código:

// Uglified JavaScript production code:
export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}

En el fragmento anterior, puedes ver que la variable legible por humanos scriptElement en la fuente se abrevia a t. Cuando se aplica a una gran colección de secuencias de comandos, los ahorros pueden ser bastante significativos, sin afectar las funciones que proporciona el JavaScript de producción de un sitio web.

Si usas un bundler para procesar el código fuente de tu sitio web, la ofuscación a menudo se realiza automáticamente para las compilaciones de producción. Los ofuscadores, como Terser, por ejemplo, también son altamente configurables, lo que te permite ajustar la agresividad del algoritmo de ofuscación para lograr el máximo ahorro. Sin embargo, los valores predeterminados de cualquier herramienta de ofuscación suelen ser suficientes para lograr el equilibrio adecuado entre el tamaño de la salida y la conservación de las capacidades.

Demos de JavaScript

Ponga a prueba sus conocimientos

¿Cuál es la mejor manera de cargar varios archivos CSS en el navegador?

Es la declaración @import de CSS.
Vuelve a intentarlo.
Varios elementos <link>
Correcto.

¿Qué hace el escáner de precarga del navegador?

Es un analizador de HTML secundario que examina el lenguaje de marcado sin procesar para descubrir recursos antes de que lo haga el analizador de DOM y, así, descubrirlos antes.
Correcto.
Detecta elementos <link rel="preload"> en un recurso HTML.
Vuelve a intentarlo.

¿Por qué el navegador bloquea temporalmente el análisis de HTML de forma predeterminada cuando se descargan recursos de JavaScript?

Para evitar un parpadeo de contenido sin diseño (FOUC)
Vuelve a intentarlo.
Debido a que la evaluación de JavaScript es una tarea que requiere mucha CPU, pausar el análisis de HTML le da más ancho de banda a la CPU para terminar de cargar las secuencias de comandos.
Vuelve a intentarlo.
Porque las secuencias de comandos pueden modificar el DOM o acceder a él de otra manera.
Correcto.

A continuación: Cómo ayudar al navegador con sugerencias de recursos

Ahora que ya sabes cómo los recursos cargados en el elemento <head> pueden afectar la carga inicial de la página y varias métricas, es momento de continuar. En el siguiente módulo, se exploran las sugerencias de recursos y cómo pueden brindar sugerencias valiosas al navegador para que comience a cargar recursos y a abrir conexiones a servidores de origen cruzado antes de lo que lo haría sin ellas.