Domina la API Resource Timing para diagnosticar y optimizar el rendimiento frontend. Aprende a medir cada tiempo de carga de recursos, desde búsquedas DNS hasta la descarga de contenido.
Desbloqueando el Rendimiento Frontend: Un Análisis Profundo de la API Resource Timing
En el mundo del desarrollo web, la velocidad no es solo una característica; es un requisito fundamental para una experiencia de usuario positiva. Un sitio web que carga lentamente puede llevar a tasas de rebote más altas, menor interacción del usuario y, en última instancia, un impacto negativo en los objetivos de negocio. Si bien herramientas como Lighthouse y WebPageTest proporcionan diagnósticos de alto nivel invaluables, a menudo representan una única prueba sintética. Para comprender y optimizar verdaderamente el rendimiento para una audiencia global, necesitamos medir la experiencia de los usuarios reales, en sus dispositivos y en sus redes. Aquí es donde entra en juego el Monitoreo de Usuario Real (RUM, por sus siglas en inglés), y una de sus herramientas más poderosas es la API Resource Timing.
Esta guía completa te llevará a un análisis profundo de la API Resource Timing. Exploraremos qué es, cómo usarla y cómo convertir sus datos granulares en información accionable que puede mejorar drásticamente el rendimiento de carga de tu aplicación. Ya seas un ingeniero frontend experimentado o estés comenzando tu viaje en la optimización del rendimiento, este artículo te equipará con el conocimiento para diseccionar y comprender el rendimiento de la red de cada activo en tu página.
¿Qué es la API Resource Timing?
La API Resource Timing es una API de JavaScript basada en el navegador que proporciona datos detallados de sincronización de red para cada recurso que una página web descarga. Piénsalo como una lente microscópica para la actividad de red de tu página. Para cada imagen, script, hoja de estilo, fuente y llamada a la API (a través de `fetch` o `XMLHttpRequest`), esta API captura una marca de tiempo de alta resolución para cada etapa de la solicitud de red.
Es parte de un conjunto más grande de APIs de Rendimiento, que trabajan juntas para proporcionar una visión holística del rendimiento de tu aplicación. Mientras que la API Navigation Timing se centra en el ciclo de vida del documento principal, la API Resource Timing se enfoca en todos los recursos dependientes que solicita el documento principal.
¿Por qué es tan importante?
- Granularidad: Va más allá de una única métrica de "tiempo de carga de la página". Puedes ver con precisión cuánto tiempo tomaron la búsqueda de DNS, la conexión TCP y la descarga de contenido para un script de terceros específico o una imagen principal crítica.
- Datos de Usuarios Reales: A diferencia de las herramientas de laboratorio, esta API se ejecuta en los navegadores de tus usuarios. Esto te permite recopilar datos de rendimiento de una diversa gama de condiciones de red, dispositivos y ubicaciones geográficas, dándote una imagen real de tu experiencia de usuario global.
- Información Accionable: Al analizar estos datos, puedes identificar cuellos de botella específicos. ¿Un script de análisis de terceros tarda en conectarse? ¿Tu CDN tiene un rendimiento bajo en una región determinada? ¿Tus imágenes son demasiado grandes? La API Resource Timing proporciona la evidencia necesaria para responder a estas preguntas con confianza.
La Anatomía de la Carga de un Recurso: Deconstruyendo la Línea de Tiempo
El núcleo de la API Resource Timing es el objeto `PerformanceResourceTiming`. Por cada recurso cargado, el navegador crea uno de estos objetos, que contiene una gran cantidad de información sobre tiempos y tamaños. Para entender estos objetos, es útil visualizar el proceso de carga como un gráfico de cascada, donde cada paso sigue al anterior.
Desglosemos las propiedades clave de un objeto `PerformanceResourceTiming`. Todos los valores de tiempo son marcas de tiempo de alta resolución medidas en milisegundos desde el inicio de la navegación de la página (`performance.timeOrigin`).
startTime -> fetchStart -> domainLookupStart -> domainLookupEnd -> connectStart -> connectEnd -> requestStart -> responseStart -> responseEnd
Propiedades Clave de Sincronización
name: La URL del recurso. Este es tu identificador principal.entryType: Una cadena que indica el tipo de entrada de rendimiento. Para nuestros propósitos, siempre será "resource".initiatorType: Esto es increíblemente útil para la depuración. Te dice cómo se solicitó el recurso. Los valores comunes incluyen 'img', 'link' (para CSS), 'script', 'css' (para recursos cargados desde CSS como `@import`), 'fetch' y 'xmlhttprequest'.duration: El tiempo total que tardó el recurso, calculado comoresponseEnd - startTime. Esta es la métrica de nivel superior para un solo recurso.startTime: La marca de tiempo inmediatamente antes de que comience la obtención del recurso.fetchStart: La marca de tiempo justo antes de que el navegador comience a buscar el recurso. Puede verificar cachés (caché HTTP, caché de Service Worker) antes de proceder a la red. Si el recurso se sirve desde una caché, muchos de los valores de tiempo posteriores serán cero.domainLookupStart&domainLookupEnd: Marcan el inicio y el fin de la búsqueda DNS (Sistema de Nombres de Dominio). La duración (domainLookupEnd - domainLookupStart) es el tiempo que tardó en resolver el nombre de dominio a una dirección IP. Un valor alto aquí podría indicar un proveedor de DNS lento.connectStart&connectEnd: Marcan el inicio y el fin del establecimiento de una conexión con el servidor. Para HTTP, este es el handshake de tres vías TCP. La duración (connectEnd - connectStart) es tu tiempo de conexión TCP.secureConnectionStart: Si el recurso se carga sobre HTTPS, esta marca de tiempo indica el comienzo del handshake SSL/TLS. La duración (connectEnd - secureConnectionStart) te dice cuánto tiempo tomó la negociación de cifrado. Los handshakes TLS lentos pueden ser una señal de una mala configuración del servidor o de latencia de red.requestStart: La marca de tiempo justo antes de que el navegador envíe la solicitud HTTP real del recurso al servidor. El tiempo entreconnectEndyrequestStarta menudo se llama tiempo de "encolamiento de solicitud", donde el navegador está esperando una conexión disponible.responseStart: La marca de tiempo en la que el navegador recibe el primer byte de la respuesta del servidor. La duración (responseStart - requestStart) es el famoso Time to First Byte (TTFB). Un TTFB alto es casi siempre un indicador de un proceso de backend lento o latencia del lado del servidor.responseEnd: La marca de tiempo en la que se ha recibido el último byte del recurso, cerrando con éxito la solicitud. La duración (responseEnd - responseStart) representa el tiempo de descarga del contenido.
Propiedades de Tamaño del Recurso
Entender el tamaño de los recursos es tan importante como entender la sincronización. La API proporciona tres métricas clave:
transferSize: El tamaño en bytes del recurso transferido a través de la red, incluyendo las cabeceras y el cuerpo de la respuesta comprimido. Si el recurso se sirvió desde una caché, a menudo será 0. Este es el número que impacta directamente en el plan de datos del usuario y en el tiempo de red.encodedBodySize: El tamaño en bytes del cuerpo de la carga útil *después* de la compresión (por ejemplo, Gzip o Brotli) pero *antes* de la descompresión. Esto te ayuda a entender el tamaño de la carga útil en sí, separada de las cabeceras.decodedBodySize: El tamaño en bytes del cuerpo de la carga útil en su forma original, sin comprimir. Comparar esto conencodedBodySizerevela la efectividad de tu estrategia de compresión. Si estos dos números son muy cercanos para un activo basado en texto (como JS, CSS o HTML), es probable que tu compresión no esté funcionando correctamente.
Server Timing
Una de las integraciones más poderosas con la API Resource Timing es la propiedad `serverTiming`. Tu backend puede enviar métricas de rendimiento en una cabecera HTTP especial (`Server-Timing`), y estas métricas aparecerán en el array `serverTiming` del objeto `PerformanceResourceTiming` correspondiente. Esto cierra la brecha entre el monitoreo de rendimiento del frontend y el backend, permitiéndote ver los tiempos de consulta de la base de datos o los retrasos en el procesamiento de la API directamente en tus datos de frontend.
Por ejemplo, un backend podría enviar esta cabecera:
Server-Timing: db;dur=53, api;dur=47.2, cache;desc="HIT"
Estos datos estarían disponibles en la propiedad `serverTiming`, permitiéndote correlacionar un TTFB alto con un proceso lento específico en el backend.
Cómo Acceder a los Datos de Resource Timing en JavaScript
Ahora que entendemos los datos disponibles, veamos las formas prácticas de recopilarlos usando JavaScript. Hay dos métodos principales.
Método 1: `performance.getEntriesByType('resource')`
Esta es la forma más sencilla de empezar. Este método devuelve un array de todos los objetos `PerformanceResourceTiming` para los recursos que ya han terminado de cargarse en la página en el momento de la llamada.
// Espera a que la página se cargue para asegurar que la mayoría de los recursos sean capturados
window.addEventListener('load', () => {
const resources = performance.getEntriesByType('resource');
resources.forEach((resource) => {
console.log(`Recurso cargado: ${resource.name}`);
console.log(` - Tiempo total: ${resource.duration.toFixed(2)}ms`);
console.log(` - Iniciador: ${resource.initiatorType}`);
console.log(` - Tamaño de transferencia: ${resource.transferSize} bytes`);
});
});
Limitación: Este método es una instantánea en el tiempo. Si lo llamas demasiado pronto, te perderás los recursos que aún no se han cargado. Si tu aplicación carga recursos dinámicamente mucho después de la carga inicial de la página, necesitarías sondear este método repetidamente, lo cual es ineficiente.
Método 2: `PerformanceObserver` (El Enfoque Recomendado)
El `PerformanceObserver` es una forma más moderna, robusta y eficiente de recopilar entradas de rendimiento. En lugar de que tú sondees los datos, el navegador envía nuevas entradas a tu callback del observador a medida que están disponibles.
He aquí por qué es mejor:
- Asíncrono: No bloquea el hilo principal.
- Exhaustivo: Puede capturar entradas desde el principio de la carga de la página, evitando condiciones de carrera donde un script se ejecuta después de que un recurso ya se ha cargado.
- Eficiente: Evita la necesidad de sondear con `setTimeout` o `setInterval`.
Aquí hay una implementación estándar:
try {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
// Procesa cada entrada de recurso a medida que llega
if (entry.entryType === 'resource') {
console.log(`Recurso observado: ${entry.name}`);
console.log(` - Tiempo hasta el primer byte (TTFB): ${(entry.responseStart - entry.requestStart).toFixed(2)}ms`);
}
});
});
// Comienza a observar las entradas de tipo 'resource'.
// La bandera 'buffered' asegura que obtengamos las entradas que se cargaron antes de que nuestro observador fuera creado.
observer.observe({ type: 'resource', buffered: true });
// Puedes dejar de observar más tarde si es necesario
// observer.disconnect();
} catch (e) {
console.error('PerformanceObserver no es compatible con este navegador.');
}
La opción buffered: true es crítica. Le dice al observador que envíe inmediatamente todas las entradas de `resource` que ya están en el búfer de entradas de rendimiento del navegador, asegurando que obtengas una lista completa desde el principio.
Gestionando el Búfer de Rendimiento
Los navegadores tienen un límite predeterminado sobre cuántas entradas de resource timing almacenan (normalmente 150). En páginas muy complejas, este búfer puede llenarse. Cuando lo hace, el navegador dispara un evento `resourcetimingbufferfull` y no se añaden nuevas entradas.
Puedes gestionar esto de la siguiente manera:
- Aumentando el tamaño del búfer: Usa `performance.setResourceTimingBufferSize(limit)` para establecer un límite más alto, por ejemplo, 300.
- Limpiando el búfer: Usa `performance.clearResourceTimings()` después de haber procesado las entradas para hacer espacio para nuevas.
performance.addEventListener('resourcetimingbufferfull', () => {
console.warn('El búfer de Resource Timing está lleno. Limpiando...');
// Procesa primero las entradas existentes desde tu observador
// Luego limpia el búfer
performance.clearResourceTimings();
// Podrías necesitar reajustar el tamaño del búfer si esto ocurre con frecuencia
// performance.setResourceTimingBufferSize(500);
});
Casos de Uso Prácticos e Información Accionable
Recopilar datos es solo el primer paso. El verdadero valor reside en convertir esos datos en mejoras accionables. Exploremos algunos problemas de rendimiento comunes y cómo la API Resource Timing te ayuda a resolverlos.
Caso de Uso 1: Identificando Scripts de Terceros Lentos
El Problema: Los scripts de terceros para análisis, publicidad, widgets de soporte al cliente y pruebas A/B son conocidos por matar el rendimiento. Pueden ser lentos para cargar, bloquear el renderizado e incluso causar inestabilidad.
La Solución: Usa la API Resource Timing para aislar y medir el impacto de estos scripts en tus usuarios reales.
const observer = new PerformanceObserver((list) => {
const thirdPartyScripts = list.getEntries().filter(entry =>
entry.initiatorType === 'script' &&
!entry.name.startsWith(window.location.origin)
);
thirdPartyScripts.forEach(script => {
if (script.duration > 200) { // Establece un umbral, ej., 200ms
console.warn(`Script de tercero lento detectado: ${script.name}`, {
duration: `${script.duration.toFixed(2)}ms`,
transferSize: `${script.transferSize} bytes`
});
// En una herramienta RUM real, enviarías estos datos a tu backend de análisis.
}
});
});
observer.observe({ type: 'resource', buffered: true });
Información Accionable:
- Duración Alta: Si un script tiene consistentemente una duración larga, considera si es realmente necesario. ¿Puede su funcionalidad ser reemplazada por una alternativa más eficiente?
- Estrategia de Carga: ¿Se está cargando el script de forma síncrona? Usa los atributos `async` o `defer` en la etiqueta `<script>` para evitar que bloquee el renderizado de la página.
- Alojar Selectivamente: ¿Se puede cargar el script condicionalmente, solo en las páginas donde es absolutamente necesario?
Caso de Uso 2: Optimizando la Entrega de Imágenes
El Problema: Las imágenes grandes y no optimizadas son una de las causas más comunes de cargas de página lentas, especialmente en dispositivos móviles con ancho de banda limitado.
La Solución: Filtra las entradas de recursos por `initiatorType: 'img'` y analiza su tamaño y tiempos de carga.
// ... dentro de un callback de PerformanceObserver ...
list.getEntries()
.filter(entry => entry.initiatorType === 'img')
.forEach(image => {
const downloadTime = image.responseEnd - image.responseStart;
// Una imagen grande puede tener un tiempo de descarga alto y un transferSize grande
if (downloadTime > 500 || image.transferSize > 100000) { // 500ms o 100KB
console.log(`Posible problema con imagen grande: ${image.name}`, {
downloadTime: `${downloadTime.toFixed(2)}ms`,
transferSize: `${(image.transferSize / 1024).toFixed(2)} KB`
});
}
});
Información Accionable:
- `transferSize` y `downloadTime` altos: Esta es una señal clara de que la imagen es demasiado grande. Optimízala usando formatos modernos como WebP o AVIF, comprimiéndola adecuadamente y redimensionándola a sus dimensiones de visualización.
- Usa `srcset`: Implementa imágenes responsivas usando el atributo `srcset` para servir diferentes tamaños de imagen según el viewport del usuario.
- Carga Diferida (Lazy Loading): Para imágenes que no son visibles al cargar la página, usa `loading="lazy"` para diferir su carga hasta que el usuario se desplace hasta ellas.
Caso de Uso 3: Diagnosticando Cuellos de Botella en la Red
El Problema: A veces, el problema no es el recurso en sí, sino la ruta de red hasta él. DNS lentos, conexiones con latencia o servidores sobrecargados pueden degradar el rendimiento.
La Solución: Desglosa la `duration` en sus fases componentes para identificar el origen del retraso.
function analyzeNetworkPhases(resource) {
const dnsTime = resource.domainLookupEnd - resource.domainLookupStart;
const tcpTime = resource.connectEnd - resource.connectStart;
const ttfb = resource.responseStart - resource.requestStart;
const downloadTime = resource.responseEnd - resource.responseStart;
console.log(`Análisis para ${resource.name}`);
if (dnsTime > 50) console.warn(` - Tiempo de DNS alto: ${dnsTime.toFixed(2)}ms`);
if (tcpTime > 100) console.warn(` - Tiempo de conexión TCP alto: ${tcpTime.toFixed(2)}ms`);
if (ttfb > 300) console.warn(` - TTFB alto (servidor lento): ${ttfb.toFixed(2)}ms`);
if (downloadTime > 500) console.warn(` - Descarga de contenido lenta: ${downloadTime.toFixed(2)}ms`);
}
// ... llama a analyzeNetworkPhases(entry) dentro de tu observador ...
Información Accionable:
- Tiempo de DNS Alto: Tu proveedor de DNS podría ser lento. Considera cambiar a un proveedor global más rápido. También puedes usar `` para resolver el DNS de dominios de terceros críticos con antelación.
- Tiempo de TCP Alto: Esto indica latencia al establecer la conexión. una Red de Entrega de Contenidos (CDN) puede reducir esto sirviendo activos desde una ubicación geográficamente más cercana al usuario. Usar `` puede realizar tanto la búsqueda de DNS como el handshake TCP de forma anticipada.
- TTFB Alto: Esto apunta a un backend lento. Trabaja con tu equipo de backend para optimizar las consultas a la base de datos, mejorar el almacenamiento en caché del lado del servidor o actualizar el hardware del servidor. La cabecera `Server-Timing` es tu mejor aliada aquí.
- Tiempo de Descarga Alto: Esto es una función del tamaño del recurso y el ancho de banda de la red. Optimiza el activo (comprime, minifica) o usa una CDN para mejorar el rendimiento.
Limitaciones y Consideraciones
Aunque es increíblemente poderosa, la API Resource Timing tiene algunas limitaciones importantes que debes conocer.
Recursos de Origen Cruzado y la Cabecera `Timing-Allow-Origin`
Por razones de seguridad, los navegadores restringen los detalles de sincronización disponibles para recursos cargados desde un origen (dominio, protocolo o puerto) diferente al de tu página principal. Por defecto, para un recurso de origen cruzado, la mayoría de las propiedades de tiempo como `redirectStart`, `domainLookupStart`, `connectStart`, `requestStart`, `responseStart`, y propiedades de tamaño como `transferSize` serán cero.
Para exponer estos detalles, el servidor que aloja el recurso debe incluir la cabecera HTTP `Timing-Allow-Origin` (TAO). Por ejemplo:
Timing-Allow-Origin: * (Permite que cualquier origen vea los detalles de tiempo)
Timing-Allow-Origin: https://www.your-website.com (Permite solo a tu sitio web)
Esto es crucial cuando trabajas con tus propias CDNs o APIs en diferentes subdominios. Asegúrate de que estén configurados para enviar la cabecera TAO para que puedas obtener una visibilidad completa del rendimiento.
Soporte de Navegadores
La API Resource Timing, incluyendo `PerformanceObserver`, es ampliamente compatible con todos los navegadores modernos (Chrome, Firefox, Safari, Edge). Sin embargo, en navegadores más antiguos, puede que no esté disponible. Siempre envuelve tu código en un bloque `try...catch` o verifica la existencia de `window.PerformanceObserver` antes de usarlo para evitar errores en clientes heredados.
Conclusión: De los Datos a las Decisiones
La API Resource Timing es un instrumento esencial en el conjunto de herramientas del desarrollador web moderno. Desmitifica la cascada de la red, proporcionando los datos brutos y granulares necesarios para pasar de quejas vagas como "el sitio es lento" a diagnósticos precisos y basados en datos como "nuestro widget de chat de terceros tiene un TTFB de 400ms para los usuarios en el sudeste asiático".
Al aprovechar `PerformanceObserver` para recopilar datos de usuarios reales y analizar el ciclo de vida completo de cada recurso, puedes:
- Hacer que los proveedores de terceros rindan cuentas por su rendimiento.
- Validar la efectividad de tu CDN y estrategias de caché en todo el mundo.
- Encontrar y corregir imágenes de gran tamaño y activos no optimizados.
- Correlacionar los retrasos del frontend con los tiempos de procesamiento del backend.
El viaje hacia una web más rápida es continuo. Empieza hoy. Abre la consola de desarrollador de tu navegador, ejecuta los fragmentos de código de este artículo en tu propio sitio y comienza a explorar los ricos datos de rendimiento que te han estado esperando todo este tiempo. Al medir lo que importa, puedes construir experiencias más rápidas, resilientes y agradables para todos tus usuarios, dondequiera que estén en el mundo.