Domine a API Resource Timing para diagnosticar e otimizar a performance do frontend. Aprenda a medir o tempo de carregamento de cada recurso, desde buscas DNS até o download de conteúdo.
Desvendando a Performance do Frontend: Um Mergulho Profundo na API Resource Timing
No mundo do desenvolvimento web, a velocidade não é apenas uma funcionalidade; é um requisito fundamental para uma experiência de usuário positiva. Um site de carregamento lento pode levar a taxas de rejeição mais altas, menor engajamento do usuário e, por fim, um impacto negativo nos objetivos de negócio. Embora ferramentas como Lighthouse e WebPageTest forneçam diagnósticos de alto nível inestimáveis, elas geralmente representam um único teste sintético. Para entender e otimizar verdadeiramente a performance para uma audiência global, precisamos medir a experiência de usuários reais, em seus dispositivos, em suas redes. É aqui que entra o Monitoramento de Usuário Real (RUM), e uma de suas ferramentas mais poderosas é a API Resource Timing.
Este guia abrangente levará você a um mergulho profundo na API Resource Timing. Exploraremos o que é, como usá-la e como transformar seus dados granulares em insights acionáveis que podem melhorar drasticamente a performance de carregamento da sua aplicação. Seja você um engenheiro de frontend experiente ou apenas começando sua jornada de otimização de performance, este artigo o equipará com o conhecimento para dissecar e entender a performance de rede de cada ativo em sua página.
O que é a API Resource Timing?
A API Resource Timing é uma API JavaScript baseada no navegador que fornece dados detalhados de tempo de rede para cada recurso que uma página web baixa. Pense nela como uma lente microscópica para a atividade de rede da sua página. Para cada imagem, script, folha de estilo, fonte e chamada de API (via `fetch` ou `XMLHttpRequest`), esta API captura um carimbo de data/hora de alta resolução para cada estágio da requisição de rede.
Ela faz parte de um conjunto maior de APIs de Performance, que trabalham juntas para fornecer uma visão holística da performance da sua aplicação. Enquanto a API Navigation Timing foca no ciclo de vida do documento principal, a API Resource Timing foca em todos os recursos dependentes que o documento principal solicita.
Por que ela é tão importante?
- Granularidade: Ela vai além de uma única métrica de "tempo de carregamento da página". Você pode ver precisamente quanto tempo a busca DNS, a conexão TCP e o download de conteúdo levaram para um script de terceiros específico ou para uma imagem principal crítica.
- Dados de Usuários Reais: Diferente de ferramentas baseadas em laboratório, esta API é executada nos navegadores dos seus usuários. Isso permite que você colete dados de performance de uma ampla gama de condições de rede, dispositivos e localizações geográficas, fornecendo uma imagem real da sua experiência de usuário global.
- Insights Acionáveis: Ao analisar esses dados, você pode identificar gargalos específicos. Um script de análise de terceiros está lento para conectar? Sua CDN está com baixo desempenho em uma determinada região? Suas imagens são muito grandes? A API Resource Timing fornece as evidências necessárias para responder a essas perguntas com confiança.
A Anatomia do Carregamento de um Recurso: Desconstruindo a Linha do Tempo
O cerne da API Resource Timing é o objeto `PerformanceResourceTiming`. Para cada recurso carregado, o navegador cria um desses objetos, que contém uma riqueza de informações de tempo e tamanho. Para entender esses objetos, é útil visualizar o processo de carregamento como um gráfico de cascata, onde cada passo segue o anterior.
Vamos detalhar as propriedades-chave de um objeto `PerformanceResourceTiming`. Todos os valores de tempo são carimbos de data/hora de alta resolução medidos em milissegundos a partir do início da navegação da página (`performance.timeOrigin`).
startTime -> fetchStart -> domainLookupStart -> domainLookupEnd -> connectStart -> connectEnd -> requestStart -> responseStart -> responseEnd
Principais Propriedades de Tempo
name: A URL do recurso. Este é o seu identificador principal.entryType: Uma string que indica o tipo de entrada de performance. Para nossos propósitos, será sempre "resource".initiatorType: Isto é incrivelmente útil para depuração. Informa como o recurso foi solicitado. Valores comuns incluem 'img', 'link' (para CSS), 'script', 'css' (para recursos carregados de dentro do CSS como `@import`), 'fetch' e 'xmlhttprequest'.duration: O tempo total gasto pelo recurso, calculado comoresponseEnd - startTime. Esta é a métrica de nível superior para um único recurso.startTime: O carimbo de data/hora imediatamente antes do início da busca do recurso.fetchStart: O carimbo de data/hora pouco antes de o navegador começar a buscar o recurso. Ele pode verificar caches (cache HTTP, cache de Service Worker) antes de prosseguir para a rede. Se o recurso for servido de um cache, muitos dos valores de tempo subsequentes serão zero.domainLookupStart&domainLookupEnd: Marcam o início e o fim da busca DNS (Domain Name System). A duração (domainLookupEnd - domainLookupStart) é o tempo que levou para resolver o nome de domínio para um endereço IP. Um valor alto aqui pode indicar um provedor de DNS lento.connectStart&connectEnd: Marcam o início e o fim do estabelecimento de uma conexão com o servidor. Para HTTP, este é o handshake TCP de três vias. A duração (connectEnd - connectStart) é o seu tempo de conexão TCP.secureConnectionStart: Se o recurso for carregado sobre HTTPS, este carimbo de data/hora marca o início do handshake SSL/TLS. A duração (connectEnd - secureConnectionStart) informa quanto tempo a negociação de criptografia levou. Handshakes TLS lentos podem ser um sinal de má configuração do servidor ou latência de rede.requestStart: O carimbo de data/hora pouco antes de o navegador enviar a requisição HTTP real para o recurso ao servidor. O tempo entreconnectEnderequestStarté frequentemente chamado de tempo de "enfileiramento de requisição", onde o navegador está esperando por uma conexão disponível.responseStart: O carimbo de data/hora quando o navegador recebe o primeiro byte da resposta do servidor. A duração (responseStart - requestStart) é o famoso Time to First Byte (TTFB). Um TTFB alto é quase sempre um indicador de um processo de backend lento ou latência do lado do servidor.responseEnd: O carimbo de data/hora quando o último byte do recurso foi recebido, fechando com sucesso a requisição. A duração (responseEnd - responseStart) representa o tempo de download do conteúdo.
Propriedades de Tamanho do Recurso
Entender o tamanho do recurso é tão importante quanto entender o tempo. A API fornece três métricas principais:
transferSize: O tamanho em bytes do recurso transferido pela rede, incluindo cabeçalhos e o corpo da resposta comprimido. Se o recurso foi servido de um cache, este valor será frequentemente 0. Este é o número que impacta diretamente o plano de dados do usuário e o tempo de rede.encodedBodySize: O tamanho em bytes do corpo da carga útil *após* a compressão (ex: Gzip ou Brotli) mas *antes* da descompressão. Isso ajuda a entender o tamanho da carga útil em si, separadamente dos cabeçalhos.decodedBodySize: O tamanho em bytes do corpo da carga útil em sua forma original, não comprimida. Comparar este valor com oencodedBodySizerevela a eficácia da sua estratégia de compressão. Se esses dois números forem muito próximos para um ativo baseado em texto (como JS, CSS ou HTML), sua compressão provavelmente não está funcionando corretamente.
Server Timing
Uma das integrações mais poderosas com a API Resource Timing é a propriedade `serverTiming`. Seu backend pode enviar métricas de performance em um cabeçalho HTTP especial (`Server-Timing`), e essas métricas aparecerão no array `serverTiming` no objeto `PerformanceResourceTiming` correspondente. Isso preenche a lacuna entre o monitoramento de performance de frontend e backend, permitindo que você veja tempos de consulta de banco de dados ou atrasos no processamento da API diretamente nos seus dados de frontend.
Por exemplo, um backend poderia enviar este cabeçalho:
Server-Timing: db;dur=53, api;dur=47.2, cache;desc="HIT"
Esses dados estariam disponíveis na propriedade `serverTiming`, permitindo que você correlacione um TTFB alto com um processo lento específico no backend.
Como Acessar os Dados da Resource Timing em JavaScript
Agora que entendemos os dados disponíveis, vamos ver as maneiras práticas de coletá-los usando JavaScript. Existem dois métodos principais.
Método 1: `performance.getEntriesByType('resource')`
Esta é a maneira mais simples de começar. Este método retorna um array de todos os objetos `PerformanceResourceTiming` para recursos que já terminaram de carregar na página no momento da chamada.
// Espere a página carregar para garantir que a maioria dos recursos seja capturada
window.addEventListener('load', () => {
const resources = performance.getEntriesByType('resource');
resources.forEach((resource) => {
console.log(`Recurso Carregado: ${resource.name}`);
console.log(` - Tempo Total: ${resource.duration.toFixed(2)}ms`);
console.log(` - Iniciador: ${resource.initiatorType}`);
console.log(` - Tamanho de Transferência: ${resource.transferSize} bytes`);
});
});
Limitação: Este método é um instantâneo no tempo. Se você o chamar muito cedo, perderá os recursos que ainda não carregaram. Se sua aplicação carrega recursos dinamicamente muito depois do carregamento inicial da página, você precisaria consultar este método repetidamente, o que é ineficiente.
Método 2: `PerformanceObserver` (A Abordagem Recomendada)
O `PerformanceObserver` é uma maneira mais moderna, robusta e performática de coletar entradas de performance. Em vez de você consultar os dados, o navegador envia novas entradas para o seu callback do observador assim que elas se tornam disponíveis.
Veja por que é melhor:
- Assíncrono: Não bloqueia a thread principal.
- Abrangente: Pode capturar entradas desde o início do carregamento da página, evitando condições de corrida onde um script é executado depois que um recurso já carregou.
- Eficiente: Evita a necessidade de consultar com `setTimeout` ou `setInterval`.
Aqui está uma implementação padrão:
try {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
// Processa cada entrada de recurso à medida que ela chega
if (entry.entryType === 'resource') {
console.log(`Recurso observado: ${entry.name}`);
console.log(` - Time to First Byte (TTFB): ${(entry.responseStart - entry.requestStart).toFixed(2)}ms`);
}
});
});
// Começa a observar por entradas 'resource'.
// A flag 'buffered' garante que recebamos entradas que carregaram antes de nosso observador ser criado.
observer.observe({ type: 'resource', buffered: true });
// Você pode parar de observar mais tarde, se necessário
// observer.disconnect();
} catch (e) {
console.error('PerformanceObserver não é suportado neste navegador.');
}
A opção buffered: true é crítica. Ela diz ao observador para despachar imediatamente todas as entradas `resource` que já estão no buffer de entrada de performance do navegador, garantindo que você obtenha uma lista completa desde o início.
Gerenciando o Buffer de Performance
Os navegadores têm um limite padrão de quantas entradas de resource timing eles armazenam (geralmente 150). Em páginas muito complexas, este buffer pode encher. Quando isso acontece, o navegador dispara um evento `resourcetimingbufferfull`, e nenhuma nova entrada é adicionada.
Você pode gerenciar isso:
- Aumentando o tamanho do buffer: Use `performance.setResourceTimingBufferSize(limit)` para definir um limite maior, por exemplo, 300.
- Limpando o buffer: Use `performance.clearResourceTimings()` depois de ter processado as entradas para abrir espaço para novas.
performance.addEventListener('resourcetimingbufferfull', () => {
console.warn('O buffer de Resource Timing está cheio. Limpando...');
// Processe as entradas existentes do seu observador primeiro
// Depois limpe o buffer
performance.clearResourceTimings();
// Você pode precisar reajustar o tamanho do buffer se isso acontecer com frequência
// performance.setResourceTimingBufferSize(500);
});
Casos de Uso Práticos e Insights Acionáveis
Coletar dados é apenas o primeiro passo. O valor real está em transformar esses dados em melhorias acionáveis. Vamos explorar alguns problemas comuns de performance e como a API Resource Timing ajuda a resolvê-los.
Caso de Uso 1: Identificando Scripts de Terceiros Lentos
O Problema: Scripts de terceiros para análises, publicidade, widgets de suporte ao cliente e testes A/B são notórios assassinos de performance. Eles podem ser lentos para carregar, bloquear a renderização e até causar instabilidade.
A Solução: Use a API Resource Timing para isolar e medir o impacto desses scripts em seus usuários reais.
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) { // Defina um limite, ex: 200ms
console.warn(`Script lento de terceiros detectado: ${script.name}`, {
duration: `${script.duration.toFixed(2)}ms`,
transferSize: `${script.transferSize} bytes`
});
// Em uma ferramenta RUM real, você enviaria esses dados para o seu backend de analytics.
}
});
});
observer.observe({ type: 'resource', buffered: true });
Insights Acionáveis:
- Duração Alta: Se um script consistentemente tem uma longa duração, considere se ele é realmente necessário. Sua funcionalidade pode ser substituída por uma alternativa mais performática?
- Estratégia de Carregamento: O script está sendo carregado de forma síncrona? Use os atributos `async` ou `defer` na tag `<script>` para evitar que ele bloqueie a renderização da página.
- Hospedagem Seletiva: O script pode ser carregado condicionalmente, apenas nas páginas onde é absolutamente necessário?
Caso de Uso 2: Otimizando a Entrega de Imagens
O Problema: Imagens grandes e não otimizadas são uma das causas mais comuns de carregamentos de página lentos, especialmente em dispositivos móveis com largura de banda limitada.
A Solução: Filtre as entradas de recursos por `initiatorType: 'img'` e analise seus tamanhos e tempos de carregamento.
// ... dentro de um callback do PerformanceObserver ...
list.getEntries()
.filter(entry => entry.initiatorType === 'img')
.forEach(image => {
const downloadTime = image.responseEnd - image.responseStart;
// Uma imagem grande pode ter um alto tempo de download e um transferSize grande
if (downloadTime > 500 || image.transferSize > 100000) { // 500ms ou 100KB
console.log(`Possível problema com imagem grande: ${image.name}`, {
downloadTime: `${downloadTime.toFixed(2)}ms`,
transferSize: `${(image.transferSize / 1024).toFixed(2)} KB`
});
}
});
Insights Acionáveis:
- `transferSize` e `downloadTime` altos: Este é um sinal claro de que a imagem é muito grande. Otimize-a usando formatos modernos como WebP ou AVIF, comprimindo-a adequadamente e redimensionando-a para suas dimensões de exibição.
- Use `srcset`: Implemente imagens responsivas usando o atributo `srcset` para servir diferentes tamanhos de imagem com base na viewport do usuário.
- Lazy Loading: Para imagens abaixo da dobra, use `loading="lazy"` para adiar seu carregamento até que o usuário as role para a visualização.
Caso de Uso 3: Diagnosticando Gargalos de Rede
O Problema: Às vezes, o problema não é o recurso em si, mas o caminho da rede até ele. DNS lento, conexões latentes ou servidores sobrecarregados podem degradar a performance.
A Solução: Decomponha a `duration` em suas fases componentes para identificar a origem do atraso.
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álise para ${resource.name}`);
if (dnsTime > 50) console.warn(` - Tempo de DNS alto: ${dnsTime.toFixed(2)}ms`);
if (tcpTime > 100) console.warn(` - Tempo de conexão TCP alto: ${tcpTime.toFixed(2)}ms`);
if (ttfb > 300) console.warn(` - TTFB alto (servidor lento): ${ttfb.toFixed(2)}ms`);
if (downloadTime > 500) console.warn(` - Download de conteúdo lento: ${downloadTime.toFixed(2)}ms`);
}
// ... chame analyzeNetworkPhases(entry) dentro do seu observador ...
Insights Acionáveis:
- Tempo de DNS Alto: Seu provedor de DNS pode ser lento. Considere mudar para um provedor global mais rápido. Você também pode usar `` para resolver o DNS de domínios críticos de terceiros antecipadamente.
- Tempo de TCP Alto: Isso indica latência no estabelecimento da conexão. Uma Rede de Distribuição de Conteúdo (CDN) pode reduzir isso servindo ativos de um local geograficamente mais próximo do usuário. Usar `` pode realizar tanto a busca de DNS quanto o handshake TCP mais cedo.
- TTFB Alto: Isso aponta para um backend lento. Trabalhe com sua equipe de backend para otimizar consultas de banco de dados, melhorar o cache do lado do servidor ou atualizar o hardware do servidor. O cabeçalho `Server-Timing` é seu melhor amigo aqui.
- Tempo de Download Alto: Esta é uma função do tamanho do recurso e da largura de banda da rede. Otimize o ativo (comprima, minifique) ou use uma CDN para melhorar a taxa de transferência.
Limitações e Considerações
Embora incrivelmente poderosa, a API Resource Timing tem algumas limitações importantes a serem consideradas.
Recursos de Origem Cruzada e o Cabeçalho `Timing-Allow-Origin`
Por razões de segurança, os navegadores restringem os detalhes de tempo disponíveis para recursos carregados de uma origem (domínio, protocolo ou porta) diferente da sua página principal. Por padrão, para um recurso de origem cruzada, a maioria das propriedades de tempo como `redirectStart`, `domainLookupStart`, `connectStart`, `requestStart`, `responseStart` e propriedades de tamanho como `transferSize` serão zero.
Para expor esses detalhes, o servidor que hospeda o recurso deve incluir o cabeçalho HTTP `Timing-Allow-Origin` (TAO). Por exemplo:
Timing-Allow-Origin: * (Permite que qualquer origem veja os detalhes de tempo)
Timing-Allow-Origin: https://www.your-website.com (Permite apenas o seu site)
Isso é crucial ao trabalhar com suas próprias CDNs ou APIs em subdomínios diferentes. Garanta que eles estejam configurados para enviar o cabeçalho TAO para que você possa obter visibilidade total da performance.
Suporte dos Navegadores
A API Resource Timing, incluindo o `PerformanceObserver`, é amplamente suportada em todos os navegadores modernos (Chrome, Firefox, Safari, Edge). No entanto, para navegadores mais antigos, ela pode não estar disponível. Sempre envolva seu código em um bloco `try...catch` ou verifique a existência de `window.PerformanceObserver` antes de usá-lo para evitar erros em clientes legados.
Conclusão: Dos Dados às Decisões
A API Resource Timing é um instrumento essencial no kit de ferramentas do desenvolvedor web moderno. Ela desmistifica a cascata de rede, fornecendo os dados brutos e granulares necessários para passar de reclamações vagas de "o site está lento" para diagnósticos precisos e baseados em dados como "nosso widget de chat de terceiros tem um TTFB de 400ms para usuários no Sudeste Asiático."
Ao aproveitar o `PerformanceObserver` para coletar dados de usuários reais e analisar o ciclo de vida completo de cada recurso, você pode:
- Responsabilizar provedores de terceiros por sua performance.
- Validar a eficácia de sua CDN e estratégias de cache em todo o mundo.
- Encontrar e corrigir imagens superdimensionadas e ativos não otimizados.
- Correlacionar atrasos de frontend com tempos de processamento de backend.
A jornada para uma web mais rápida é contínua. Comece hoje. Abra o console de desenvolvedor do seu navegador, execute os trechos de código deste artigo em seu próprio site e comece a explorar os ricos dados de performance que estiveram esperando por você o tempo todo. Ao medir o que importa, você pode construir experiências mais rápidas, mais resilientes e mais agradáveis para todos os seus usuários, onde quer que eles estejam no mundo.