Optimizar el retraso de primera entrada

Cómo responder con mayor rapidez a las interacciones de los usuarios

Hice clic, pero no sucedió nada. ¿Por qué no puedo interactuar con esta página? 😢

First Contentful Paint (FCP) y Largest Contentful Paint (LCP) son métricas que miden el tiempo que tarda el contenido en renderizarse (pintar) visualmente en una página. Aunque es importante, los tiempos de pintura no capturan la capacidad de respuesta de la carga, es decir, la rapidez con la que una página responde a la interacción del usuario.

El retraso de primera entrada (FID) es una métrica de Métricas web esenciales que captura la primera impresión que tiene un usuario respecto de la interactividad y la capacidad de respuesta de un sitio. Mide el tiempo desde que un usuario interactúa por primera vez con una página hasta el momento en que el navegador puede responder a esa interacción. FID es una métrica de campo y no se puede simular en un entorno de lab. Se requiere una interacción real del usuario para medir el retraso de la respuesta.

Los valores correctos de fid son de 2.5 segundos, los valores deficientes son mayores a 4.0 segundos y cualquier valor intermedio requiere mejoras

Para ayudar a predecir el FID en el lab, recomendamos el Tiempo de bloqueo total (TBT). Miden diferentes aspectos, pero las mejoras en TBT suelen corresponder a mejoras en FID.

La causa principal de un FID deficiente es la ejecución pesada de JavaScript. La optimización del análisis, la compilación y la ejecución de JavaScript en tu página web reducirá el FID de forma directa.

Ejecución intensa de JavaScript

El navegador no puede responder a la mayoría de las entradas del usuario mientras ejecuta JavaScript en el subproceso principal. En otras palabras, el navegador no puede responder a las interacciones del usuario mientras el subproceso principal está ocupado. Para mejorar esto:

Divide las tareas largas

Si ya intentaste reducir la cantidad de JavaScript que se carga en una sola página, puede resultar útil dividir el código de larga duración en tareas asíncronas más pequeñas.

Las tareas largas son períodos de ejecución de JavaScript en los que los usuarios pueden encontrar que tu IU no responde. Cualquier fragmento de código que bloquee el subproceso principal durante 50 ms o más se puede caracterizar como una tarea larga. Las tareas largas son un signo de un posible sobredimensionamiento de JavaScript (cargar y ejecutar más de lo que un usuario puede necesitar en este momento). Dividir tareas largas puede reducir la demora en las entradas de tu sitio.

Tareas largas en las Herramientas para desarrolladores de Chrome
Las Herramientas para desarrolladores de Chrome visualizan las tareas largas en el panel de rendimiento

FID debería mejorar notablemente a medida que adoptas prácticas recomendadas, como la división de código y la división de tus tareas largas. Si bien la TBT no es una métrica de campo, es útil para verificar el progreso con el objetivo de mejorar, en última instancia, el tiempo de carga (TTI) y el FID.

Cómo optimizar tu página para permitir la interacción

Existen varias causas comunes de las puntuaciones FID y TBT bajas en las apps web que dependen en gran medida de JavaScript:

La ejecución de secuencias de comandos propias puede retrasar la preparación de las interacciones.

  • El sobredimensionamiento del tamaño de JavaScript, los tiempos de ejecución intensos y la fragmentación ineficiente pueden ralentizar la rapidez con la que una página puede responder a las entradas del usuario y afectar el FID, TBT y TTI. La carga progresiva de código y funciones puede ayudar a distribuir este trabajo y mejorar la preparación para la interacción.
  • Es posible que parezca que las apps renderizadas en el servidor tienen píxeles pintados en la pantalla rápidamente, pero ten en cuenta que las interacciones del usuario se bloquean con ejecuciones grandes de secuencias de comandos (p.ej., la rehidratación para conectar objetos de escucha de eventos). Esto puede tardar varios cientos de milisegundos, a veces incluso segundos, si se usa la división de código basada en rutas. Considera cambiar más lógica del servidor o generar más contenido de forma estática durante el tiempo de compilación.

A continuación, se muestran las puntuaciones TBT antes y después de optimizar la carga de secuencias de comandos propias para una aplicación. Al quitar de la ruta crítica la costosa carga (y ejecución) de secuencias de comandos de un componente no esencial, los usuarios pudieron interactuar con la página mucho antes.

Mejoras en la puntuación TBT en Lighthouse después de optimizar la secuencia de comandos propia

La recuperación de datos puede afectar muchos aspectos de la preparación para las interacciones

  • Esperar una cascada de recuperaciones en cascada (p.ej., recuperaciones de JavaScript y de datos para componentes) puede afectar la latencia de la interacción. Intenta minimizar la dependencia de las recuperaciones de datos en cascada.
  • Los grandes almacenes de datos intercalados pueden enviar el tiempo de análisis de HTML y afectar las métricas de pintura y de interacción. Procura minimizar la cantidad de datos que el cliente debe procesar posteriormente.

La ejecución de secuencias de comandos de terceros también puede retrasar la latencia de las interacciones

  • Muchos sitios incluyen etiquetas y análisis de terceros que pueden mantener la red ocupada y hacer que el subproceso principal deje de responder periódicamente, lo que afecta la latencia de interacción. Explorar la carga a pedido del código de terceros (p. ej., es posible que no cargues los anuncios de la mitad inferior de la página hasta que se desplacen más cerca del viewport)
  • En algunos casos, las secuencias de comandos de terceros pueden adelantar a las propias en términos de prioridad y ancho de banda en el subproceso principal, lo que también retrasa la rapidez con la que una página está lista para la interacción. Intenta priorizar primero la carga de lo que crees que ofrece el mayor valor a los usuarios.

Usa un trabajador web

Un subproceso principal bloqueado es una de las principales causas de retraso de entrada. Los trabajadores web permiten ejecutar JavaScript en un subproceso en segundo plano. Mover operaciones que no son de IU a un subproceso de trabajo separado puede reducir el tiempo de bloqueo del subproceso principal y, en consecuencia, mejorar FID.

Considera usar las siguientes bibliotecas para facilitar el uso de trabajadores web en tu sitio:

  • Comlink: Es una biblioteca auxiliar que abstrae postMessage y facilita su uso.
  • Workway: Un exportador de trabajador web de uso general.
  • Workerize: Mueve un módulo a un trabajador web.

Reduce el tiempo de ejecución de JavaScript

Limitar la cantidad de JavaScript de tu página reduce la cantidad de tiempo que el navegador necesita para ejecutar código JavaScript. Esto acelera la velocidad con la que el navegador puede comenzar a responder a las interacciones del usuario.

Para reducir la cantidad de código JavaScript que se ejecuta en tu página, haz lo siguiente:

  • Aplaza el código JavaScript sin usar
  • Minimiza los polyfills que no se usan

Aplaza el código JavaScript sin usar

De forma predeterminada, todo JavaScript bloquea el procesamiento. Cuando el navegador encuentra una etiqueta de secuencia de comandos que se vincula a un archivo JavaScript externo, debe pausar su tarea y descargar, analizar, compilar y ejecutar ese JavaScript. Por lo tanto, solo debes cargar el código necesario para la página o que responda a las entradas del usuario.

La pestaña Cobertura de las Herramientas para desarrolladores de Chrome puede indicarte cuánto JavaScript no se usa en tu página web.

La pestaña Cobertura

Para reducir el uso de JavaScript sin usar, haz lo siguiente:

  • Divide el paquete en varios fragmentos
  • Aplaza cualquier JavaScript que no sea crítico, incluidas las secuencias de comandos de terceros, con async o defer.

La división de código es el concepto de dividir un paquete grande de JavaScript en fragmentos más pequeños que se pueden cargar de forma condicional (también conocida como carga diferida). La mayoría de los navegadores más recientes admiten la sintaxis de importación dinámica, que permite la recuperación de módulos a pedido:

import('module.js').then((module) => {
  // Do something with the module.
});

La importación dinámica de JavaScript en determinadas interacciones del usuario (como cambiar una ruta o mostrar una ventana modal) garantizará que el código que no se use para la carga inicial de la página solo se recupere cuando sea necesario.

Además de la compatibilidad general con los navegadores, la sintaxis de importación dinámica se puede usar en muchos sistemas de compilación diferentes.

  • Si usas webpack, Rollup o Parcel como agrupador de módulos, aprovecha su compatibilidad con la importación dinámica.
  • Los frameworks del cliente, como React, Angular y Vue, proporcionan abstracciones para facilitar la carga diferida a nivel de los componentes.

Además de la división de código, siempre usa async o defer para las secuencias de comandos que no son necesarias para el contenido de la mitad superior de la página o la ruta de acceso crítica.

<script defer src="…"></script>
<script async src="…"></script>

A menos que haya un motivo específico para no hacerlo, todas las secuencias de comandos de terceros deben cargarse con defer o async de forma predeterminada.

Minimiza los polyfills que no se usan

Si creas tu código con la sintaxis moderna de JavaScript y haces referencia a las APIs de navegadores actualizados, deberás transpilarlo e incluir polyfills para que funcione en navegadores anteriores.

Una de las principales preocupaciones de rendimiento de incluir polyfills y el código transpilado en tu sitio es que los navegadores más nuevos no deberían descargarlo si no lo necesitan. Para reducir el tamaño de JavaScript de tu aplicación, minimiza los polyfills sin usar tanto como sea posible y restringe su uso a los entornos en los que sean necesarios.

Para optimizar el uso de polyfills en tu sitio, haz lo siguiente:

  • Si usas Babel como transpilador, usa @babel/preset-env para incluir solo los polyfills necesarios para los navegadores a los que deseas orientar los anuncios. En Babel 7.9, habilita la opción bugfixes para reducir aún más los polyfills innecesarios.
  • Usa el patrón de módulo/no módulo para entregar dos paquetes separados (@babel/preset-env también lo admite a través de target.esmodules)

    <script type="module" src="modern.js"></script>
    <script nomodule src="legacy.js" defer></script>
    

    Muchas de las funciones más recientes de ECMAScript compiladas con Babel ya son compatibles con entornos que admiten módulos de JavaScript. De esta manera, simplificas el proceso de asegurarte de que solo se use el código transpilado para los navegadores que realmente lo necesiten.

Herramientas para desarrolladores

Hay una serie de herramientas disponibles para medir y depurar FID:

Gracias a Philip Walton, Kayce Vasques, Ilya Grigorik y Annie Sullivan por sus opiniones.