Cómo evaluar el rendimiento de carga en el campo con Navigation Timing y Resource Timing

Aprende los aspectos básicos del uso de las APIs de Navigation Timing y Resource Timing para evaluar el rendimiento de carga en el campo.

Publicado el 8 de octubre de 2021

Si usaste la limitación de conexión en el panel de red de las Herramientas para desarrolladores de un navegador (o Lighthouse en Chrome) para evaluar el rendimiento de carga, sabes lo convenientes que son esas herramientas para el ajuste de rendimiento. Puedes medir rápidamente el impacto de las optimizaciones de rendimiento con una velocidad de conexión de referencia coherente y estable. El único problema es que se trata de pruebas sintéticas, que producen datos de laboratorio, no datos de campo.

Las pruebas sintéticas no son malas en sí mismas, pero no representan la velocidad con la que se carga tu sitio web para los usuarios reales. Eso requiere datos de campo, que puedes recopilar de las APIs de Navigation Timing y Resource Timing.

APIs para ayudarte a evaluar el rendimiento de carga en el campo

Navigation Timing y Resource Timing son dos APIs similares con una superposición significativa que miden dos cosas distintas:

  • Navigation Timing mide la velocidad de las solicitudes de documentos HTML (es decir, las solicitudes de navegación).
  • Resource Timing mide la velocidad de las solicitudes de recursos dependientes de documentos, como CSS, JavaScript, imágenes y otros tipos de recursos.

Estas APIs exponen sus datos en un búfer de entrada de rendimiento, al que se puede acceder en el navegador con JavaScript. Hay varias formas de consultar un búfer de rendimiento, pero una forma común es usar performance.getEntriesByType:

// Get Navigation Timing entries:
performance.getEntriesByType('navigation');

// Get Resource Timing entries:
performance.getEntriesByType('resource');

performance.getEntriesByType acepta una cadena que describe el tipo de entradas que deseas recuperar del búfer de entrada de rendimiento. 'navigation' y 'resource' recuperan los tiempos de las APIs de Navigation Timing y Resource Timing, respectivamente.

La cantidad de información que proporcionan estas APIs puede ser abrumadora, pero son la clave para medir el rendimiento de carga en el campo, ya que puedes recopilar estos tiempos de los usuarios mientras visitan tu sitio web.

El ciclo de vida y los tiempos de una solicitud de red

Recopilar y analizar los tiempos de navegación y recursos es como la arqueología, ya que reconstruyes el ciclo de vida fugaz de una solicitud de red después del hecho. A veces, ayuda a visualizar conceptos, y en lo que respecta a las solicitudes de red, las Herramientas para desarrolladores de tu navegador pueden ayudarte.

Tiempos de red, como se muestran en las Herramientas para desarrolladores de Chrome. Los tiempos que se muestran corresponden a la puesta en cola de la solicitud, la negociación de la conexión, la solicitud en sí y la respuesta en barras codificadas por color.
Visualización de una solicitud de red en el panel de red de las Herramientas para desarrolladores de Chrome

El ciclo de vida de una solicitud de red tiene fases distintas, como la búsqueda de DNS, el establecimiento de conexión, la negociación de TLS y otras fuentes de latencia. Estos tiempos se representan como un DOMHighResTimestamp. Según tu navegador, la granularidad de los tiempos puede ser de hasta microsegundos o redondearse a milisegundos. Te recomendamos que examines estas fases en detalle y cómo se relacionan con Navigation Timing y Resource Timing.

búsqueda de DNS

Cuando un usuario va a una URL, se consulta el Sistema de nombres de dominio (DNS) para traducir un dominio a una dirección IP. Este proceso puede llevar mucho tiempo, incluso el que deseas medir en el campo. Navigation Timing y Resource Timing exponen dos tiempos relacionados con el DNS:

  • domainLookupStart es cuando comienza la búsqueda de DNS.
  • domainLookupEnd es cuando finaliza la búsqueda de DNS.

Para calcular el tiempo total de búsqueda de DNS, resta la métrica de inicio de la métrica de finalización:

// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;

Negociación de conexión

Otro factor que contribuye al rendimiento de carga es la negociación de conexión, que es la latencia que se produce cuando se conecta a un servidor web. Si se incluye HTTPS, este proceso también incluirá el tiempo de negociación de TLS. La fase de conexión consta de tres tiempos:

  • connectStart es cuando el navegador comienza a abrir una conexión a un servidor web.
  • secureConnectionStart marca el momento en que el cliente comienza la negociación de TLS.
  • connectEnd es cuando se estableció la conexión con el servidor web.

Medir el tiempo total de conexión es similar a medir el tiempo total de búsqueda de DNS: restas el tiempo de inicio del tiempo de finalización. Sin embargo, hay una propiedad adicional secureConnectionStart que puede ser 0 si no se usa HTTPS o si la conexión es persistente. Si deseas medir el tiempo de negociación de TLS, deberás tenerlo en cuenta:

// Quantifying total connection time
const [pageNav] = performance.getEntriesByType('navigation');
const connectionTime = pageNav.connectEnd - pageNav.connectStart;
let tlsTime = 0; // <-- Assume 0 to start with

// Was there TLS negotiation?
if (pageNav.secureConnectionStart > 0) {
  // Awesome! Calculate it!
  tlsTime = pageNav.connectEnd - pageNav.secureConnectionStart;
}

Una vez que finalizan la búsqueda de DNS y la negociación de conexión, entran en juego los tiempos relacionados con la recuperación de documentos y sus recursos dependientes.

Solicitudes y respuestas

El rendimiento de carga se ve afectado por dos tipos de factores:

  • Factores extrínsecos: Son elementos como la latencia y el ancho de banda. Más allá de elegir una empresa de hosting y posiblemente una CDN, están (en su mayoría) fuera de nuestro control, ya que los usuarios pueden acceder a la Web desde cualquier lugar.
  • Factores intrínsecos: Son elementos como las arquitecturas del servidor y del cliente, así como el tamaño de los recursos y nuestra capacidad de optimizar esos elementos, que están dentro de nuestro control.

Ambos tipos de factores afectan el rendimiento de carga. Los tiempos relacionados con estos factores son fundamentales, ya que describen cuánto tardan los recursos en descargarse. Navigation Timing y Resource Timing describen el rendimiento de carga con las siguientes métricas:

  • fetchStart marca el momento en que el navegador comienza a recuperar un recurso (Resource Timing) o un documento para una solicitud de navegación (Navigation Timing). Esto precede a la solicitud real y es el punto en el que el navegador verifica las memorias caché (por ejemplo, instancias HTTP y Cache).
  • workerStart marca el momento en que se comienza a controlar una solicitud dentro del controlador de eventos fetch de un service worker. Será 0 cuando ningún service worker controle la página actual.
  • requestStart es cuando el navegador realiza la solicitud.
  • responseStart es cuando llega el primer byte de la respuesta.
  • responseEnd es cuando llega el último byte de la respuesta.

Estos tiempos te permiten medir varios aspectos del rendimiento de carga, como la búsqueda en caché dentro de un service worker y el tiempo de descarga:

// Cache seek plus response time of the current document
const [pageNav] = performance.getEntriesByType('navigation');
const fetchTime = pageNav.responseEnd - pageNav.fetchStart;

// Service worker time plus response time
let workerTime = 0;

if (pageNav.workerStart > 0) {
  workerTime = pageNav.responseEnd - pageNav.workerStart;
}

También puedes medir otros aspectos de la latencia de solicitud y respuesta:

const [pageNav] = performance.getEntriesByType('navigation');

// Request time only (excluding redirects, DNS, and connection/TLS time)
const requestTime = pageNav.responseStart - pageNav.requestStart;

// Response time only (download)
const responseTime = pageNav.responseEnd - pageNav.responseStart;

// Request + response time
const requestResponseTime = pageNav.responseEnd - pageNav.requestStart;

Otras mediciones que puedes realizar

Navigation Timing y Resource Timing son útiles para mucho más de lo que se describe en los ejemplos anteriores. Estas son algunas otras situaciones con tiempos relevantes que pueden valer la pena explorar:

  • Redireccionamientos de página: Los redireccionamientos son una fuente olvidada de latencia agregada, en especial las cadenas de redireccionamiento. La latencia se agrega de varias maneras, como los saltos de HTTP a HTTPS, así como los redireccionamientos 302/301 sin caché. Los tiempos redirectStart, redirectEnd y redirectCount son útiles para evaluar la latencia de redireccionamiento.
  • Descarga de documentos: En las páginas que ejecutan código en un unload controlador de eventos, el navegador debe ejecutar ese código antes de poder navegar a la página siguiente. unloadEventStart y unloadEventEnd miden la descarga de documentos.
  • Procesamiento de documentos: Es posible que el tiempo de procesamiento de documentos no sea importante, a menos que tu sitio web envíe cargas útiles HTML muy grandes. Si esta es tu situación, los tiempos domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd y domComplete pueden ser de interés.

Cómo obtener tiempos en tu código

Todos los ejemplos que se muestran hasta ahora usan performance.getEntriesByType, pero hay otras formas de consultar el búfer de entrada de rendimiento, como performance.getEntriesByName y performance.getEntries. Estos métodos son adecuados cuando solo se necesita un análisis ligero. Sin embargo, en otras situaciones, pueden introducir un trabajo excesivo del subproceso principal iterando sobre una gran cantidad de entradas o incluso sondeando repetidamente el búfer de rendimiento para encontrar entradas nuevas.

El enfoque recomendado para recopilar entradas del búfer de entrada de rendimiento es usar un PerformanceObserver. PerformanceObserver escucha las entradas de rendimiento y las proporciona a medida que se agregan al búfer:

// Create the performance observer:
const perfObserver = new PerformanceObserver((observedEntries) => {
  // Get all resource entries collected so far:
  const entries = observedEntries.getEntries();

  // Iterate over entries:
  for (let i = 0; i < entries.length; i++) {
    // Do the work!
  }
});

// Run the observer for Navigation Timing entries:
perfObserver.observe({
  type: 'navigation',
  buffered: true
});

// Run the observer for Resource Timing entries:
perfObserver.observe({
  type: 'resource',
  buffered: true
});

Este método de recopilación de tiempos puede parecer incómodo en comparación con el acceso directo al búfer de entrada de rendimiento, pero es preferible a vincular el subproceso principal con un trabajo que no tiene un propósito crítico y orientado al usuario.

Cómo llamar a casa

Una vez que hayas recopilado todos los tiempos que necesitas, puedes enviarlos a un extremo para su análisis posterior. Hay dos formas de hacerlo: con navigator.sendBeacon o con un fetch con la opción keepalive establecida. Ambos métodos enviarán una solicitud a un endpoint especificado de forma no bloqueadora, y la solicitud se pondrá en cola de una manera que sobreviva a la sesión de página actual si es necesario:

// Check for navigator.sendBeacon support:
if ('sendBeacon' in navigator) {
  // Caution: If you have lots of performance entries, don't
  // do this. This is an example for illustrative purposes.
  const data = JSON.stringify(performance.getEntries());

  // Send the data!
  navigator.sendBeacon('/analytics', data);
}

En este ejemplo, la cadena JSON llegará en una carga útil POST que puedes decodificar, procesar y almacenar en un backend de aplicación según sea necesario.

Conclusión

Una vez que tengas las métricas recopiladas, depende de ti descubrir cómo analizar esos datos de campo. Cuando analices datos de campo, debes seguir algunas reglas generales para asegurarte de extraer conclusiones significativas:

  • Evita los promedios, ya que no representan la experiencia de ningún usuario y pueden estar sesgados por valores atípicos.
  • Confía en los percentiles. En los conjuntos de datos de métricas de rendimiento basadas en el tiempo, cuanto más bajo sea el valor, mejor. Esto significa que, cuando priorizas los percentiles bajos, solo prestas atención a las experiencias más rápidas.
  • Prioriza la cola larga de valores. Cuando priorizas las experiencias en el percentil 75 o superior, te enfocas en lo que corresponde: las experiencias más lentas.

Esta guía no pretende ser un recurso exhaustivo sobre Navigation Timing o Resource Timing, sino un punto de partida. Estos son algunos recursos adicionales que pueden resultarte útiles:

Con estas APIs y los datos que proporcionan, estarás mejor equipado para comprender cómo experimentan el rendimiento de carga los usuarios reales, lo que te dará más confianza para diagnosticar y abordar los problemas de rendimiento de carga en el campo.