Como avaliar o desempenho de carregamento no campo com Navigation Timing e Resource Timing

Aprenda os conceitos básicos do uso das APIs Navigation e Resource Timing para avaliar o desempenho de carregamento no campo.

Se você usou a limitação de conexão no painel de rede nas ferramentas para desenvolvedores de um navegador (ou o Lighthouse no Chrome) para avaliar o desempenho do carregamento, sabe que essas ferramentas são úteis para ajustar o desempenho. É possível medir rapidamente o impacto das otimizações de desempenho com um valor de referência de velocidade de conexão consistente e estável. O único problema é que os testes sintéticos geram dados de laboratório, não dados de campo.

Os testes sintéticos não são ruins inerentemente, mas não representam 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 o desempenho de carregamento no campo

Navigation Timing e Resource Timing são duas APIs semelhantes com sobreposições significativas que medem duas coisas distintas:

  • O Navigation Timing mede a velocidade das solicitações de documentos HTML (ou seja, solicitações de navegação).
  • O Resource Timing mede a velocidade das solicitações de recursos dependentes de documentos como CSS, JavaScript, imagens etc.

Essas APIs expõem os dados em um buffer de entrada de desempenho, que pode ser acessado no navegador com JavaScript. Há várias maneiras de consultar um buffer de desempenho, mas uma maneira 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 desempenho. 'navigation' e 'resource' recuperam os tempos para as APIs Navigation Timing e Resource Timing, respectivamente.

A quantidade de informações que essas APIs fornecem pode ser assustadora, mas elas são sua chave para medir o desempenho de carregamento em campo, pois você pode coletar esses tempos dos usuários à medida que eles visitam seu site.

A vida útil e o tempo de uma solicitação de rede

A coleta e a análise de tempos de navegação e recursos é como a arqueologia, na qual você reconstrua a vida passageira de uma solicitação de rede após o fato. Às vezes, é útil visualizar conceitos e, no que diz respeito às solicitações de rede, as ferramentas para desenvolvedores do navegador podem ajudar.

Um diagrama de tempo de rede, conforme mostrado no DevTools do Chrome. Os tempos descritos são para enfileiramento de solicitações, negociação de conexão, a própria solicitação e a resposta em barras codificadas por cores.
Visualização de uma solicitação de rede no painel de rede do DevTools do Chrome

A vida de uma solicitação de rede tem fases distintas, como busca de DNS, estabelecimento de conexão, negociação de TLS e assim por diante. Esses tempos são representados como DOMHighResTimestamp. Dependendo do navegador, a granularidade dos tempos pode ser de até microssegundos ou arredondada para milissegundos. Vamos 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) é consultado para traduzir um domínio para um endereço IP. Esse processo pode levar muito tempo, e até mesmo tempo você quer medir em campo. Navigation Timing e Resource Timing expõem dois tempos relacionados a DNS:

  • domainLookupStart é quando a busca DNS começa.
  • domainLookupEnd é quando a busca DNS termina.

Para calcular o tempo total da busca DNS, subtraia a métrica inicial da final:

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

Negociação da conexão

Outro fator que contribui para o desempenho do carregamento é a negociação da conexão, que é a latência incorrida ao se conectar a um servidor da Web. Se HTTPS estiver envolvido, esse processo também incluirá o tempo de negociação do TLS. A fase de conexão tem três tempos:

  • connectStart é quando o navegador começa a abrir uma conexão com um servidor da Web.
  • secureConnectionStart marca quando o cliente inicia a negociação de TLS.
  • connectEnd é quando a conexão com o servidor da Web foi estabelecida.

Medir o tempo total de conexão é semelhante à medição do tempo total de busca DNS: você subtrai o horário de início do horário de término. No entanto, há uma propriedade secureConnectionStart adicional que poderá ser 0 se o HTTPS não for usado ou se a conexão for persistente. Para medir o tempo de negociação do TLS, tenha em mente o seguinte:

// 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 da conexão terminarem, os prazos relacionados à busca de documentos e aos recursos dependentes deles entram em vigor.

Solicitações e respostas

O desempenho do carregamento é afetado por dois tipos de fatores:

  • Fatores extrínsecos:são fatores como latência e largura de banda. Além de escolher uma empresa de hospedagem e uma CDN, eles estão (na maioria) fora de nosso controle, pois os usuários podem acessar a Web de qualquer lugar.
  • Fatores intrínsecos:são coisas como arquiteturas do lado do servidor e do cliente, além do tamanho dos recursos e da nossa capacidade de otimizar para esses fatores, que estão sob nosso controle.

Ambos os tipos de fatores afetam o desempenho do carregamento. Os tempos relacionados a esses fatores são vitais, porque descrevem o tempo necessário para baixar os recursos. Tanto Navigation Timing quanto Resource Timing descrevem o desempenho de carregamento com as seguintes métricas:

  • fetchStart marca 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 os caches (por exemplo, instâncias HTTP e Cache).
  • workerStart marca quando uma solicitação começa a ser processada no manipulador de eventos fetch de um service worker. Isso será 0 quando 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 do desempenho do carregamento, como pesquisa de cache em um service worker e 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;
}

Você também pode medir outros aspectos da latência de solicitação/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 outras coisas além do que os exemplos acima descrevem. Confira algumas outras situações com tempos relevantes que podem ser úteis:

  • Redirecionamentos de página:os redirecionamentos são uma fonte de latência maior, especialmente as cadeias de redirecionamento. A latência é adicionada de várias maneiras, como saltos de HTTP para HTTPs, bem como redirecionamentos 302/sem cache. Os tempos de redirectStart, redirectEnd e redirectCount são úteis para avaliar a latência de redirecionamento.
  • Descarregamento de documentos:em páginas que executam código em um manipulador de eventos unload, o navegador precisa executar esse código antes de navegar para a próxima página. unloadEventStart e unloadEventEnd medem o descarregamento de documentos.
  • Processamento de documentos:o tempo de processamento de documentos pode não ser consequente, a menos que o site envie payloads HTML muito grandes. Se isso descreve sua situação, os horários de domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd e domComplete podem ser interessantes.

Como obter tempos no código do aplicativo

Todos os exemplos mostrados até agora usam performance.getEntriesByType, mas existem outras maneiras de consultar o buffer de entrada de desempenho, como performance.getEntriesByName e performance.getEntries. Esses métodos funcionam bem quando apenas a análise leve é necessária. No entanto, em outras situações, elas podem introduzir um excesso de trabalho da linha de execução principal iterando um grande número de entradas ou até mesmo pesquisando repetidamente o buffer de desempenho para encontrar novas entradas.

A abordagem recomendada para coletar entradas do buffer de entrada de desempenho é usar um PerformanceObserver. PerformanceObserver detecta entradas de desempenho 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 ser estranho em comparação com o acesso direto ao buffer de entrada de desempenho, mas é preferível vincular a linha de execução principal a um trabalho que não tenha uma finalidade crítica e voltada ao usuário.

Ligando para casa

Depois de coletar todos os tempos necessários, você pode enviá-los a um endpoint para análise mais aprofundada. Duas maneiras de fazer isso são com navigator.sendBeacon ou um fetch com a opção keepalive definida. Os dois métodos vão enviar uma solicitação para um endpoint especificado sem bloqueio, e a solicitação vai ser colocada em fila de modo que persista à sessão da página atual, se necessário:

// 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()));

// The endpoint to transmit the encoded data to
const endpoint = '/analytics';

// Check for fetch keepalive support
if ('keepalive' in Request.prototype) {
  fetch(endpoint, {
    method: 'POST',
    body: data,
    keepalive: true,
    headers: {
      'Content-Type': 'application/json'
    }
  });
} else if ('sendBeacon' in navigator) {
  // Use sendBeacon as a fallback
  navigator.sendBeacon(endpoint, data);
}

Neste exemplo, a string JSON chegará em um payload POST que você pode decodificar e processar/armazenar em um back-end de aplicativo conforme necessário.

Conclusão

Depois de coletar as métricas, cabe a você descobrir como analisar os dados de campo. Ao analisar dados de campo, algumas regras gerais precisam ser seguidas para garantir que você chegue a conclusões significativas:

  • Evite médias, já que elas não representam a experiência de nenhum usuário e podem ser distorcidas por outliers.
  • Confie em percentis. Em conjuntos de dados de métricas de desempenho baseadas em tempo, quanto menor, melhor. Isso significa que, quando você prioriza percentis baixos, está prestando atenção apenas às experiências mais rápidas.
  • Priorize a cauda longa de valores. Ao priorizar experiências no 75o percentil ou mais, você coloca o foco no lugar certo: nas experiências mais lentas.

Este guia não é um recurso completo sobre Navigation ou Resource Timing, mas sim um ponto de partida. Veja a seguir alguns recursos adicionais que podem ser úteis:

Com essas APIs e os dados que elas fornecem, você terá uma melhor preparação para entender como os usuários reais experimentam o desempenho de carregamento, o que lhe dará mais confiança no diagnóstico e na resolução de problemas de desempenho de carregamento no campo.