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 e Resource Timing per valutare il rendimento del caricamento sul campo.

Data di pubblicazione: 8 ottobre 2021

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

I test sintetici non sono cattivi per natura, ma non sono rappresentativi della velocità di caricamento del tuo sito web per gli utenti reali. Ciò richiede dati sul campo, che puoi raccogliere dalle API Navigation Timing e Resource Timing.

API per aiutarti a valutare le prestazioni di caricamento sul campo

Tempi navigazione e Tempi risorse sono due API simili con sovrapposizione significativa che misurano due aspetti distinti:

  • Tempi di navigazione misura la velocità delle richieste di documenti HTML (ovvero richieste di navigazione).
  • Tempi di risorse misura la velocità delle richieste di risorse dipendenti dal documento, come CSS, JavaScript, immagini e altri tipi di risorse.

Queste API mostrano i propri dati in un buffer di voci sul rendimento, a cui è possibile accedere nel browser con JavaScript. Esistono diversi modi per eseguire query su un buffer di prestazioni, 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 da recuperare dal buffer delle voci sul rendimento. 'navigation' e 'resource' recuperano i tempi rispettivamente per le API Navigation Timing e Resource Timing.

La quantità di informazioni fornite da queste API può essere travolgente, ma sono fondamentali per misurare le prestazioni di caricamento sul campo, in quanto puoi raccogliere questi tempi dagli utenti quando visitano il tuo sito web.

La durata e i tempi di una richiesta di rete

La raccolta e l'analisi delle tempistiche di navigazione e delle risorse sono un po' come l'archeologia, in quanto si ricostruisce la vita fugace di una richiesta di rete a seguito del fatto. A volte è utile visualizzare i concetti e, se riguardano le richieste di rete, gli strumenti per sviluppatori del browser possono essere d'aiuto.

Tempi di rete come mostrato in DevTools di Chrome. I tempi rappresentati si riferiscono all'accodamento delle richieste, alla negoziazione della connessione, alla richiesta stessa e alla risposta in barre codificate per colore.
Una visualizzazione di una richiesta di rete nel riquadro della rete di Chrome DevTools

Il ciclo di vita di una richiesta di rete ha fasi distinte, come la ricerca DNS, l'instaurazione della connessione, la negoziazione TLS e altre sorgenti di latenza. Questi tempi sono rappresentati come un DOMHighResTimestamp. A seconda del browser, la granularità dei tempi può essere ridotta al microsecondo o arrotondata ai millisecondi. Ti consigliamo di esaminare nel dettaglio queste fasi e la loro relazione con il tempo di navigazione e il tempo delle risorse.

ricerca DNS

Quando un utente accede a un URL, viene eseguita una query sul DNS (Domain Name System) per tradurre un dominio in un indirizzo IP. Questa procedura potrebbe richiedere molto tempo, che ti consigliamo di misurare sul campo. Il tempo di navigazione e il tempo delle risorse mostrano due tempi relativi al DNS:

  • domainLookupStart è il momento in cui inizia la ricerca DNS.
  • domainLookupEnd corrisponde al termine della ricerca DNS.

Il calcolo del tempo totale di ricerca DNS può essere eseguito sottraendo la metrica iniziale dalla metrica finale:

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

Negoziazione della connessione

Un altro fattore che contribuisce alle prestazioni di caricamento è la negoziazione della connessione, ovvero la latenza che si verifica quando ci si connette a un server web. Se è coinvolto HTTPS, questo processo includerà anche il tempo di negoziazione TLS. La fase di connessione è composta da tre tempistiche:

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

La misurazione del tempo di connessione totale è simile a quella del tempo di ricerca DNS totale: si sottrae il tempo di inizio da quello di fine. Tuttavia, c'è un'altra proprietà secureConnectionStart che potrebbe essere 0 se non viene utilizzato HTTPS o se la connessione è permanente. Se vuoi misurare il tempo di negoziazione TLS, tieni presente quanto segue:

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

Al termine della ricerca DNS e della negoziazione della connessione, entrano in gioco i tempi relativi al recupero dei documenti e delle relative risorse dipendenti.

Richieste e risposte

Il rendimento del caricamento dipende da due tipi di fattori:

  • Fattori estrinseci:sono aspetti come la latenza e la larghezza di banda. Oltre a scegliere una società di hosting e possibilmente una CDN, sono (soprattutto) fuori dal nostro controllo, poiché gli utenti possono accedere al web ovunque.
  • Fattori intrinseci: si tratta di elementi come le architetture lato server e lato client, le dimensioni delle risorse e la nostra capacità di eseguire l'ottimizzazione in base a questi elementi, che sono sotto il nostro controllo.

Entrambi i tipi di fattori influiscono sulle prestazioni di caricamento. I tempi correlati a questi fattori sono fondamentali, in quanto descrivono il tempo necessario per il download delle risorse. Sia Navigation Timing che Resource Timing descrivono le prestazioni di caricamento con le seguenti metriche:

  • fetchStart viene contrassegnato quando il browser inizia a recuperare una risorsa (Tempi risorse) o un documento per una richiesta di navigazione (Tempi di navigazione). Questa è la prima della richiesta effettiva e il punto in cui il browser controlla le cache (ad esempio, HTTP e istanze Cache).
  • workerStart contrassegna quando inizia la gestione di una richiesta all'interno del gestore di eventi fetch di un service worker. Il 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 indica l'arrivo dell'ultimo byte della risposta.

Questi tempi ti consentono di misurare più aspetti del rendimento del caricamento, ad esempio la ricerca nella cache all'interno di un worker di servizio 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

I parametri Navigation Timing e Resource Timing sono utili per più di quanto descritto negli esempi precedenti. Ecco alcune altre situazioni con tempistiche pertinenti che potrebbero valere la pena di esaminare:

  • Reindirizzamenti di pagina:i reindirizzamenti sono una fonte trascurata di ulteriore latenza, in particolare le catene di reindirizzamento. La latenza viene aggiunta in diversi modi, ad esempio tramite i passaggi 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 del reindirizzamento.
  • Scarico del documento: nelle pagine che eseguono codice in un unload gestore eventi, il browser deve eseguire il codice prima di poter passare alla pagina successiva. unloadEventStart e unloadEventEnd misurano lo scarico dei documenti.
  • Elaborazione di documenti: il tempo di elaborazione dei documenti potrebbe non essere significativo, a meno che il tuo sito web non invii payload HTML di grandi dimensioni. Se questo descrive la tua situazione, le tempistiche 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 delle voci relative al rendimento, ad esempio performance.getEntriesByName e performance.getEntries. Questi metodi sono accettabili quando è necessaria solo l'analisi luminosa. In altre situazioni, tuttavia, possono introdurre un lavoro eccessivo nel thread principale eseguendo l'iterazione su un numero elevato di voci o addirittura eseguendo ripetutamente il polling del buffer di prestazioni per trovarne di nuove.

L'approccio consigliato per raccogliere le voci dal buffer delle voci sul rendimento è utilizzare un PerformanceObserver. PerformanceObserver ascolta le voci relative al 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 potrebbe sembrare sconveniente rispetto all'accesso diretto al buffer delle voci relative alle prestazioni, ma è preferibile occupare il thread principale con attività che non hanno uno scopo critico e rivolto agli utenti.

Come chiamare a casa

Dopo aver raccolto tutti i tempi necessari, puoi inviarli a un endpoint per ulteriori analisi. Puoi farlo in due modi: con navigator.sendBeacon o con fetch con l'opzione keepalive impostata. Entrambi i metodi inviano una richiesta a un endpoint specificato in modo non bloccante e la richiesta verrà messa in coda in modo da sopravvivere 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 memorizzare in un backend dell'applicazione in base alle esigenze.

Conclusione

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

  • Evita i valori medi, poiché non sono rappresentativi dell'esperienza di alcun utente e potrebbero essere distorti da valori anomali.
  • Affidati ai percentili. Nei set di dati delle metriche sul rendimento basate sul tempo, un valore più basso è migliore. Ciò significa che quando dai la priorità a percentili bassi, presti attenzione solo alle esperienze più veloci.
  • Dai la priorità alla coda lunga dei valori. Quando dai priorità alle esperienze al 75° percentile o superiore, stai concentrando la tua attenzione su cui appartiene: le esperienze più lente.

Questa guida non intende essere una risorsa esaustiva sulla navigazione o sui tempi delle risorse, ma un punto di partenza. Ecco alcune risorse aggiuntive che potrebbero esserti utili:

Con queste API e i dati che forniscono, avrai a disposizione gli strumenti necessari per comprendere meglio l'esperienza degli utenti reali in termini di prestazioni di caricamento, il che ti consentirà di diagnosticare e risolvere più facilmente i problemi di prestazioni di caricamento sul campo.