Come valutare le prestazioni di caricamento sul campo con Navigation Timing e Resource Timing

Scopri le nozioni di base per l'utilizzo delle API Navigation Timing e Resource Timing per valutare il rendimento del caricamento sul campo.

Pubblicato l'8 ottobre 2021

Se hai utilizzato la limitazione della connessione nel riquadro Rete degli strumenti per sviluppatori di un browser (o Lighthouse in Chrome) per valutare il rendimento del caricamento, sai quanto sono utili questi strumenti per l'ottimizzazione del rendimento. Puoi misurare rapidamente l'impatto delle ottimizzazioni del rendimento con una velocità di connessione di base coerente e stabile. L'unico problema è che si tratta di test sintetici, che producono dati di laboratorio, non dati sul campo.

I test sintetici non sono intrinsecamente negativi, ma non sono rappresentativi della velocità di caricamento del tuo sito web per gli utenti reali. Per questo sono necessari dati sul campo, che puoi raccogliere dalle API Navigation Timing e Resource Timing.

API per valutare il rendimento del caricamento sul campo

Navigation Timing e Resource Timing sono due API simili con una sovrapposizione significativa che misurano due cose distinte:

  • Navigation Timing misura la velocità delle richieste di documenti HTML (ovvero le richieste di navigazione).
  • Resource Timing misura la velocità delle richieste di risorse dipendenti dai documenti, come CSS, JavaScript, immagini e altri tipi di risorse.

Queste API espongono i loro dati in un buffer di voci di rendimento, a cui è possibile accedere nel browser con JavaScript. Esistono diversi modi per eseguire query su un buffer di rendimento, ma un modo comune è utilizzare performance.getEntriesByType:

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

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

performance.getEntriesByType accetta una stringa che descrive il tipo di voci che vuoi recuperare dal buffer di voci di rendimento. 'navigation' e 'resource' recuperano rispettivamente i tempi delle API Navigation Timing e Resource Timing.

La quantità di informazioni fornite da queste API può essere eccessiva, ma sono la chiave per misurare il rendimento del caricamento sul campo, in quanto puoi raccogliere questi tempi dagli utenti mentre visitano il tuo sito web.

La durata e i tempi di una richiesta di rete

La raccolta e l'analisi dei tempi di navigazione e delle risorse sono una sorta di archeologia, in quanto ricostruisci la durata effimera di una richiesta di rete dopo il fatto. A volte è utile visualizzare i concetti e, per quanto riguarda le richieste di rete, gli strumenti per sviluppatori del browser possono aiutarti.

Tempi di rete visualizzati in DevTools di Chrome. Le tempistiche mostrate si riferiscono alla coda delle richieste, alla negoziazione della connessione, alla richiesta stessa e alla risposta in barre codificate a colori.
Una visualizzazione di una richiesta di rete nel riquadro Rete di Chrome DevTools

La durata di una richiesta di rete ha fasi distinte, come la ricerca DNS, l'instaurazione della connessione, la negoziazione TLS e altre fonti di latenza. Questi tempi sono rappresentati come un DOMHighResTimestamp. A seconda del browser, la granularità dei tempi può essere fino al microsecondo o arrotondata ai millisecondi. Ti consigliamo di esaminare queste fasi in dettaglio e il loro rapporto con Navigation Timing e Resource Timing.

Ricerca DNS

Quando un utente visita un URL, viene eseguita una query sul Domain Name System (DNS) per tradurre un dominio in un indirizzo IP. Questa procedura può richiedere molto tempo, che ti consigliamo di misurare sul campo. Navigation Timing e Resource Timing espongono due tempi correlati al DNS:

  • domainLookupStart è il momento in cui inizia la ricerca DNS.
  • domainLookupEnd è il momento in cui termina la ricerca DNS.

Per calcolare il tempo totale di ricerca DNS, sottrai la metrica di inizio dalla metrica di fine:

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

Negoziato della connessione

Un altro fattore che contribuisce al rendimento del caricamento è il negoziato della connessione, ovvero la latenza che si verifica quando ci si connette a un server web. Se è coinvolto HTTPS, questa procedura includerà anche il tempo di negoziato TLS. La fase di connessione è costituita da tre tempi:

  • connectStart è il momento in cui il browser inizia ad aprire una connessione a un server web.
  • secureConnectionStart indica quando il client inizia il negoziato TLS.
  • connectEnd è il momento in cui è stata stabilita la connessione al server web.

La misurazione del tempo totale di connessione è simile alla misurazione del tempo totale di ricerca DNS: sottrai il tempo di inizio dal tempo di fine. Tuttavia, esiste una proprietà aggiuntiva secureConnectionStart che può essere 0 se non viene utilizzato HTTPS o se la connessione è persistente. Se vuoi misurare il tempo di negoziato TLS, devi tenerlo presente:

// 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 volta terminati la ricerca DNS e il negoziato della connessione, entrano in gioco i tempi relativi al recupero dei documenti e delle risorse dipendenti.

Richieste e risposte

Il rendimento del caricamento è influenzato da due tipi di fattori:

  • Fattori estrinseci: si tratta di elementi come la latenza e la larghezza di banda. Oltre a scegliere una società di hosting ed eventualmente una CDN, sono (per lo più) fuori dal nostro controllo, in quanto gli utenti possono accedere al web da qualsiasi luogo.
  • Fattori intrinseci: si tratta di elementi come le architetture lato server e lato client, nonché le dimensioni delle risorse e la nostra capacità di ottimizzare questi elementi, che sono sotto il nostro controllo.

Entrambi i tipi di fattori influiscono sul rendimento del caricamento. I tempi relativi a questi fattori sono fondamentali, in quanto descrivono il tempo necessario per scaricare le risorse. Sia Navigation Timing sia Resource Timing descrivono il rendimento del caricamento con le seguenti metriche:

  • fetchStart indica quando il browser inizia a recuperare una risorsa (Resource Timing) o un documento per una richiesta di navigazione (Navigation Timing). Questa operazione precede la richiesta effettiva ed è il punto in cui il browser controlla le cache (ad esempio, le istanze HTTP e Cache instances).
  • workerStart indica quando una richiesta inizia a essere gestita all'interno del gestore di eventi fetch di un service worker. Questo valore sarà 0 quando nessun service worker controlla la pagina corrente.
  • requestStart è il momento in cui il browser effettua la richiesta.
  • responseStart è il momento in cui arriva il primo byte della risposta.
  • responseEnd è il momento in cui arriva l'ultimo byte della risposta.

Questi tempi ti consentono di misurare più aspetti del rendimento del caricamento, come la ricerca nella cache all'interno di un service worker e il tempo di 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;
}

Puoi anche misurare altri aspetti della latenza di richiesta e risposta:

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;

Altre misurazioni che puoi effettuare

Navigation Timing e Resource Timing sono utili per molto altro rispetto a quanto descritto negli esempi precedenti. Ecco alcune altre situazioni con tempi pertinenti che potrebbero valere la pena esplorare:

  • Reindirizzamenti di pagina: i reindirizzamenti sono una fonte trascurata di latenza aggiuntiva, in particolare le catene di reindirizzamento. La latenza viene aggiunta in diversi modi, ad esempio gli hop da HTTP a HTTPS, nonché i reindirizzamenti 302/301 non memorizzati nella cache. I tempi redirectStart, redirectEnd e redirectCount sono utili per valutare la latenza di reindirizzamento.
  • Scarico dei documenti: nelle pagine che eseguono codice in un unload gestore di eventi, il browser deve eseguire il codice prima di poter passare alla pagina successiva. unloadEventStart e unloadEventEnd misurano lo scarico dei documenti.
  • Elaborazione dei documenti: il tempo di elaborazione dei documenti potrebbe non essere significativo a meno che il tuo sito web non invii payload HTML molto grandi. Se questa è la tua situazione, i tempi domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd e domComplete potrebbero interessarti.

Come ottenere i tempi nel codice

Tutti gli esempi mostrati finora utilizzano performance.getEntriesByType, ma esistono altri modi per eseguire query sul buffer di voci di rendimento, ad esempio performance.getEntriesByName e performance.getEntries. Questi metodi sono adatti quando è necessaria solo un'analisi leggera. In altre situazioni, tuttavia, possono introdurre un lavoro eccessivo del thread principale iterando su un numero elevato di voci o persino eseguendo ripetutamente il polling del buffer di rendimento per trovare nuove voci.

L'approccio consigliato per la raccolta delle voci dal buffer di voci di rendimento è l'utilizzo di un PerformanceObserver. PerformanceObserver ascolta le voci di rendimento e le fornisce man mano che vengono aggiunte al 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
});

Questo metodo di raccolta dei tempi può sembrare scomodo rispetto all'accesso diretto al buffer di voci di rendimento, ma è preferibile a bloccare il thread principale con un lavoro che non ha uno scopo critico e rivolto all'utente.

Come inviare i dati

Una volta raccolti tutti i tempi necessari, puoi inviarli a un endpoint per ulteriori analisi. Due modi per farlo sono con navigator.sendBeacon o con un fetch con l'opzione keepalive impostata. Entrambi i metodi inviano una richiesta a un endpoint specificato in modo non bloccante e la richiesta viene messa in coda in modo che sopravviva alla sessione della pagina corrente, se necessario:

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

In questo esempio, la stringa JSON arriverà in un payload POST che puoi decodificare, elaborare e archiviare in un backend dell'applicazione, se necessario.

Conclusione

Una volta raccolte le metriche, sta a te capire come analizzare i dati sul campo. Quando analizzi i dati sul campo, devi seguire alcune regole generali per assicurarti di trarre conclusioni significative:

  • Evita le medie, in quanto non sono rappresentative dell'esperienza di un singolo utente e potrebbero essere distorte da valori anomali.
  • Affidati ai percentili. Nei set di dati di metriche di rendimento basate sul tempo, i valori più bassi sono migliori. Ciò significa che quando dai la priorità ai percentili bassi, presti attenzione solo alle esperienze più veloci.
  • Dai la priorità alla coda lunga dei valori. Quando dai la priorità alle esperienze al 75° percentile o superiore, concentri la tua attenzione dove deve essere: sulle esperienze più lente.

Questa guida non vuole essere una risorsa esaustiva su Navigation Timing o Resource Timing, ma un punto di partenza. Ecco alcune risorse aggiuntive che potrebbero esserti utili:

Con queste API e i dati che forniscono, sarai in grado di comprendere meglio l'esperienza del rendimento del caricamento da parte degli utenti reali, il che ti darà maggiore sicurezza nella diagnosi e nella risoluzione dei problemi di rendimento del caricamento sul campo.