Aprenda os conceitos básicos do uso das APIs Navigation e Resource Timing para avaliar a performance de carregamento em campo.
Publicado em 8 de outubro de 2021
Se você já usou a limitação de conexão no painel de rede das ferramentas para desenvolvedores de um navegador (ou Lighthouse no Chrome) para avaliar a performance de carregamento, sabe como essas ferramentas são convenientes para o ajuste de performance. É possível medir rapidamente o impacto das otimizações de performance com uma velocidade de conexão de referência consistente e estável. O único problema é que esse é um teste sintético, que gera dados de laboratório, não dados de campo.
O teste sintético não é ruim por natureza, mas não representa a velocidade de carregamento do seu site para usuários reais. Isso exige dados de campo, que podem ser coletados nas APIs Navigation Timing e Resource Timing.
APIs para ajudar a avaliar a performance de carregamento em campo
Navigation Timing e Resource Timing são duas APIs semelhantes com sobreposição significativa que medem duas coisas distintas:
- Navigation Timing mede a velocidade das solicitações de documentos HTML (ou seja, solicitações de navegação).
- Resource Timing mede a velocidade das solicitações de recursos dependentes de documentos, como CSS, JavaScript, imagens e outros tipos de recursos.
Essas APIs expõem os dados em um buffer de entrada de performance, que pode ser acessado no navegador com JavaScript. Há várias maneiras de consultar um buffer de performance, mas uma comum é usar performance.getEntriesByType:
// Get Navigation Timing entries:
performance.getEntriesByType('navigation');
// Get Resource Timing entries:
performance.getEntriesByType('resource');
performance.getEntriesByType aceita uma string que descreve o tipo de entradas que você quer recuperar do buffer de entrada de performance. 'navigation' e 'resource' recuperam os tempos das APIs Navigation Timing e Resource Timing, respectivamente.
A quantidade de informações fornecidas por essas APIs pode ser excessiva, mas elas são a chave para medir a performance de carregamento em campo, já que é possível coletar esses tempos dos usuários enquanto eles visitam seu site.
A vida e os tempos de uma solicitação de rede
A coleta e a análise dos tempos de navegação e de recursos são como a arqueologia, em que você reconstrói a vida fugaz de uma solicitação de rede após o fato. Às vezes, é útil visualizar conceitos e, no caso de solicitações de rede, as ferramentas para desenvolvedores do navegador podem ajudar.
A vida de uma solicitação de rede tem fases distintas, como busca DNS, estabelecimento de conexão, negociação de TLS e outras fontes de latência. Esses tempos são representados como um DOMHighResTimestamp. Dependendo do navegador, a granularidade dos tempos pode ser de até microssegundos ou arredondada para milissegundos. É recomendável examinar essas fases em detalhes e como elas se relacionam com Navigation Timing e Resource Timing.
busca DNS
Quando um usuário acessa um URL, o Sistema de Nomes de Domínio (DNS, na sigla em inglês) é consultado para traduzir um domínio em um endereço IP. Esse processo pode levar um tempo significativo, que você vai querer medir em campo. Navigation Timing e Resource Timing expõem dois tempos relacionados ao DNS:
domainLookupStarté quando a busca DNS começa.domainLookupEndé quando a busca DNS termina.
Para calcular o tempo total de busca DNS, subtraia a métrica de início da métrica de término:
// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;
Negociação de conexão
Outro fator que contribui para a performance de carregamento é a negociação de conexão, que é a latência incorrida ao se conectar a um servidor da Web. Se o HTTPS estiver envolvido, esse processo também vai incluir o tempo de negociação de TLS. A fase de conexão consiste em três tempos:
connectStarté quando o navegador começa a abrir uma conexão com um servidor da Web.secureConnectionStartmarca quando o cliente começa a negociação de TLS.connectEndé quando a conexão com o servidor da Web foi estabelecida.
A medição do tempo total de conexão é semelhante à medição do tempo total de busca DNS: subtraia o tempo de início do tempo de término. No entanto, há uma propriedade secureConnectionStart adicional que pode ser 0 se o HTTPS não for usado ou se a conexão for persistente. Se você quiser medir o tempo de negociação de TLS, será necessário ter isso em mente:
// 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;
}
Quando a busca DNS e a negociação de conexão terminam, os tempos relacionados à busca de documentos e seus recursos dependentes entram em jogo.
Solicitações e respostas
A performance de carregamento é afetada por dois tipos de fatores:
- Fatores extrínsecos:são coisas como latência e largura de banda. Além de escolher uma empresa de hospedagem e possivelmente uma CDN, elas estão (principalmente) fora do nosso controle, já que os usuários podem acessar a Web de qualquer lugar.
- Fatores intrínsecos:são coisas como arquiteturas de servidor e cliente, bem como o tamanho dos recursos e nossa capacidade de otimizar essas coisas, que estão sob nosso controle.
Ambos os tipos de fatores afetam a performance de carregamento. Os tempos relacionados a esses fatores são essenciais, já que descrevem quanto tempo os recursos levam para fazer o download. Navigation Timing e Resource Timing descrevem a performance de carregamento com as seguintes métricas:
fetchStartmarca quando o navegador começa a buscar um recurso (Resource Timing) ou um documento para uma solicitação de navegação (Navigation Timing). Isso precede a solicitação real e é o ponto em que o navegador está verificando caches (por exemplo, instâncias HTTP eCacheinstâncias).workerStartmarca quando uma solicitação começa a ser processada no manipulador de eventosfetchde um service worker. Isso será0quando nenhum service worker estiver controlando a página atual.requestStarté quando o navegador faz a solicitação.responseStarté quando o primeiro byte da resposta chega.responseEndé quando o último byte da resposta chega.
Esses tempos permitem medir vários aspectos da performance de carregamento, como a busca de cache em um service worker e o tempo de download:
// 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;
}
Também é possível medir outros aspectos da latência de solicitação e resposta:
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;
Outras medições que você pode fazer
Navigation Timing e Resource Timing são úteis para mais do que o que os exemplos anteriores descrevem. Confira algumas outras situações com tempos relevantes que podem valer a pena explorar:
- Redirecionamentos de página:os redirecionamentos são uma fonte negligenciada de latência adicionada, especialmente cadeias de redirecionamento. A latência é adicionada de várias maneiras, como saltos HTTP para HTTPS, bem como redirecionamentos 302/301 não armazenados em cache. Os tempos
redirectStart,redirectEnderedirectCountsão úteis para avaliar a latência de redirecionamento. - Descarregamento de documentos:em páginas que executam código em um
unloadmanipulador de eventos, o navegador precisa executar esse código antes de poder navegar para a próxima página.unloadEventStarteunloadEventEndmedem o descarregamento de documentos. - Processamento de documentos:o tempo de processamento de documentos pode não ser consequente, a menos que seu site envie payloads HTML muito grandes. Se essa for a sua situação, os tempos
domInteractive,domContentLoadedEventStart,domContentLoadedEventEndedomCompletepoderão ser interessantes.
Como receber tempos no seu código
Todos os exemplos mostrados até agora usam performance.getEntriesByType, mas há outras maneiras de consultar o buffer de entrada de performance, como performance.getEntriesByName e performance.getEntries. Esses métodos são adequados quando apenas uma análise leve é necessária. Em outras situações, no entanto, eles podem introduzir um trabalho excessivo da linha de execução principal, iterando um grande número de entradas ou até mesmo pesquisando repetidamente o buffer de performance para encontrar novas entradas.
A abordagem recomendada para coletar entradas do buffer de entrada de performance é usar um PerformanceObserver. PerformanceObserver fica atento às entradas de performance e as fornece à medida que são adicionadas ao buffer:
// 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
});
Esse método de coleta de tempos pode parecer estranho quando comparado ao acesso direto ao buffer de entrada de desempenho, mas é preferível vincular a linha de execução principal a um trabalho que não atenda a uma finalidade crítica e voltada ao usuário.
Como fazer uma chamada de retorno
Depois de coletar todos os tempos necessários, você pode enviá-los a um endpoint para análise mais detalhada. Duas maneiras de fazer isso são com navigator.sendBeacon ou um fetch com a opção keepalive definida. Ambos os métodos vão enviar uma solicitação para um endpoint especificado de maneira não bloqueadora, e a solicitação será enfileirada de uma forma que sobreviva à sessão de página atual, se necessário:
// 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);
}
Neste exemplo, a string JSON vai chegar em um payload POST que pode ser decodificado, processado e armazenado em um back-end de aplicativo conforme necessário.
Conclusão
Depois de coletar as métricas, cabe a você descobrir como analisar esses dados de campo. Ao analisar dados de campo, há algumas regras gerais a serem seguidas para garantir que você esteja tirando conclusões significativas:
- Evite médias, já que elas não são representativas da experiência de um usuário e podem ser distorcidas por outliers.
- Confie em percentis. Em conjuntos de dados de métricas de performance baseadas em tempo, quanto menor, melhor. Isso significa que, ao priorizar percentis baixos, você só está prestando atenção às experiências mais rápidas.
- Priorize a cauda longa de valores. Ao priorizar experiências no 75º percentil ou superior, você está focando onde deveria: nas experiências mais lentas.
Este guia não pretende ser um recurso exaustivo sobre Navigation ou Resource Timing, mas um ponto de partida. Confira alguns recursos adicionais que podem ser úteis:
- Especificação de Navigation Timing (em inglês).
- Especificação de Resource Timing (em inglês).
- ResourceTiming na prática (em inglês).
- API Navigation Timing (MDN) (em inglês)
- API Resource Timing (MDN) (em inglês)
Com essas APIs e os dados que elas fornecem, você estará mais bem equipado para entender como a performance de carregamento é vivenciada por usuários reais, o que vai dar mais confiança para diagnosticar e resolver problemas de performance de carregamento em campo.