Diario · Nota 001 · Prestazioni web

Dove si nascondevano gli ultimi 18 punti in un sito statico

Immagini compresse, JavaScript diviso in tre file, nessun framework. Lighthouse segnava comunque 82. I punti mancanti erano in tre aree che lo strumento nominava senza spiegare.

Serhat Belen

Fondatore, BLN Global · · 7 min di lettura

Risposta breve

Immagini e JavaScript erano già a posto. I punti mancanti si nascondevano altrove. Primo: il controllo pensato per evitare lavoro inutile leggeva il layout e costringeva il browser a ricalcolarlo. Secondo: il foglio di stile e i font esterni ritardavano di 600 ms il primo rendering. Abbiamo integrato il CSS nella pagina e ospitato i font sul nostro server. Terzo: dei 24 avvisi sul contrasto, 6 erano falsi positivi. La scansione completa ha poi trovato 13 pagine sfuggite al campione.

Questo sito non usa framework. Le pagine sono semplice HTML generato da script Python, lo stile sta in un solo file CSS e JavaScript occupa tre piccoli file. Le immagini sono in WebP e vengono prodotte in tre dimensioni. Tutti gli interventi più ovvi sulle prestazioni erano già stati fatti.

Il punteggio mobile era fermo a 82.

Sistemati i problemi evidenti, i punti rimasti non dipendevano da un unico grande errore. Erano sparsi in tre punti diversi. Per trovarli tutti, leggere gli avvisi dello strumento non bastava. cosa intendi ha richiesto di capire.

Al primo posto: il controllo scritto per evitare il lavoro

L’avviso "ricalcolo forzato" (forced reflow) di Lighthouse indica che JavaScript obbliga il browser a calcolare il layout troppo presto. Il browser accumula le modifiche agli stili per applicarle insieme. Se nel frattempo il codice legge una misura, deve elaborarle subito.

L’avviso indicava la riga sorgente, ma lì non si vedeva alcun valore. Trovato in tre passaggi.

  • Al primo giro getComputedStyle chiamate rimosse. Il punteggio non si è mosso.
  • Al secondo giro è spuntato il vero colpevole: un if (window.scrollY > 0) controllo. Il controllo stesso legge il layout. La riga scritta per evitare quel lavoro finiva per eseguirlo.
  • Al terzo tentativo abbiamo eliminato la funzione. Lo stato iniziale era già definito nel CSS. JavaScript non doveva ricrearlo.

Resta anche uno schema void element.offsetWidth era. Un trucco diffuso e accettato: forzava il layout apposta per riavviare una transizione. Lo abbiamo sostituito con due requestAnimationFrame È stato aggiunto. Fa lo stesso lavoro senza forzare il layout.

function riavvia(fn) { /* Se la scheda è in background, rAF non parte e la demo resta vuota. */ if (document.hidden) { fn(); return; } requestAnimationFrame(function () { requestAnimationFrame(fn); }); }
Riavviare la transizione senza forzare il layout. La scheda in background deve avere una via d'uscita: lì rAF non parte mai.

La lezione è questa: spesso un avviso sulle prestazioni non dice "stai facendo troppo", ma "lo stai facendo nel momento sbagliato".

Al secondo posto: i 600 ms prima del primo rendering

Il secondo avviso riguardava le risorse che bloccavano la prima visualizzazione. All’apertura, il browser non mostrava nulla finché non aveva scaricato ed elaborato prima il foglio di stile, poi i font. Il ritardo misurato era di 600 millisecondi.

Abbiamo inserito il foglio di stile nella pagina

Un file CSS separato alla seconda visita arriva dalla cache. È un vantaggio. Alla prima visita, però, aggiunge un viaggio in rete, e la prima visita è quella di chi arriva dai risultati di ricerca. Durante la pubblicazione riduciamo il CSS e lo inseriamo nella pagina. Lo abbiamo misurato: dopo la compressione Brotli, la versione incorporata pesava meno di file separato + seconda richiesta.

Abbiamo portato i font sul nostro server

L’assetto arrivato da Google Fonts chiamava due origini diverse: una per lo stile, fonts.googleapis.com, per i file fonts.gstatic.com. Ogni nuova origine richiede una risoluzione DNS, un handshake TLS e una nuova connessione. Eliminarla ha prodotto questo risultato:

250 KB165 KB

I font scaricati da una pagina in turco. Da 6 file e 2 origini esterne a 3 file su una sola origine.

Come abbiamo misurato Il CSS di Google Fonts è stato scaricato con una vera identità Chrome, unicode-range guardando i campi, abbiamo separato i subset che la pagina turca avrebbe scaricato davvero (latin + latin-ext) e misurato i file uno per uno. Dall'altra parte, in questo repository, assets/fonts la dimensione reale dei file.

Abbiamo dedicato una nota a come abbiamo ridotto i font e ai costi del passaggio a dieci lingue: dieci lingue, tre file font.

Terzo punto: dove lo strumento sbaglia

Il controllo di accessibilità ha segnalato 24 problemi di contrasto. Correggerli tutti avrebbe rovinato metà del design. 6 erano falsi positivi.

Motivo content-visibility:auto era. Questa funzione rimanda il rendering delle sezioni fuori schermo e accelera la prima visualizzazione. Esattamente ciò che volevamo. Il colore calcolato della sezione rimandata, però, non arrivava allo strumento di verifica, che segnalava «bianco su bianco». Sullo schermo non accadeva nulla del genere.

Abbiamo misurato i rapporti reali nel browser, uno per uno. 18 avvisi erano corretti e li abbiamo risolti. Gli altri 6 dipendevano da un limite dello strumento: content-visibility è stato tolto solo dalle due sezioni problematiche e lasciato nelle altre.

L’avviso di uno strumento di audit non prova che ci sia un errore da correggere. È un’ipotesi: la verifichi, poi intervieni. Questa lezione è tornata altre due volte su questo sito. Una era nella nota sul controllo lingua lo spieghiamo.

Poi c’è il danno che ci siamo fatti da soli

Per evitare salti di layout, assegna le dimensioni a ogni immagine width e height È stato aggiunto l'attributo. Scelta giusta: il browser può riservare lo spazio dell'immagine prima di scaricarla. Però i box della demo si sono rotti: alcune immagini sono state disegnate larghe 58 pixel e alte 1500 pixel.

Il motivo è questo: nell’HTML width e height gli attributi suggeriscono la resa e nel CSS height ne compilano il valore. Nel design, per quei riquadri c'è solo aspect-ratio era definito, height era vuoto. L’attributo si è infilato lì e ha falsato la percentuale. Correzione in una riga:

img{max-width:100%;height:auto;display:block}
Gli attributi restano al loro posto, continuano a evitare lo spostamento del layout. L’altezza la riprende il CSS.

E qui la scansione serve davvero

Con la home a 100 sembrava finita. Non lo era. Avevamo misurato un campione. Del sito tutte le 150 pagine le abbiamo passate una a una in Lighthouse. Risultato: 137 pagine a 100 in tutte e quattro le voci, non 13 pagine. Accessibilità, best practice e SEO erano a 100 su ogni pagina. Lo scarto stava solo nelle prestazioni.

Undici pagine su tredici erano nella stessa lingua: arabo. La più bassa era a 83.

/ar/safecoast/ 83 /ar/laya/ 84 /ar/localmind/ 84 /ar/adforge/ 85 /ar/garanti/ 85 /ar/gtin/ 86 /ar/visionguard/ 86 /ar/ 89 /ar/growthos/ 97 /ar/dream/ 99 /ar/twin/ 99
Le pagine in arabo emerse dalla scansione completa. Le due variazioni rimaste (/pl/modelmesh/ e /ru/terms.html, entrambe a 98) rientrano nelle normali oscillazioni tra misurazioni.

Lighthouse lo ha detto chiaramente: Font web caricato → ar.woff2. Il font arabo arrivava tardi e faceva ricomporre la pagina. Le metriche del font arabo di riserva erano molto diverse da Noto Sans Arabic, e lo spostamento diventava forte. La correzione riprendeva la scelta già fatta con Martian Mono: swap non optional.

0,2870

Spostamenti di layout (CLS) nelle pagine arabe

Come abbiamo misurato Dopo la modifica abbiamo rimisurato 15 pagine arabe: CLS a zero in tutte e 15. I punteggi performance sono saliti da 83-89 a 98-100. 9 pagine su 15 hanno fatto 100 pieno. Misura presa sul sito live con Lighthouse.

La vera lezione non riguarda le prestazioni. Dire «il sito vale 100» dopo aver controllato solo un campione significa parlare anche delle pagine che non hai misurato. Per mesi, quindici pagine sono rimaste ferme a 83 punti. Non le avevamo mai guardate.

Il limite del risultato

Il punteggio nasce da un test di laboratorio. Presuppone un dispositivo e una rete fissi, mentre il telefono e la connessione di chi visita il sito possono essere peggiori. Misurata più volte di seguito, la stessa pagina oscilla tra 98 e 100. Un 100 non significa «veloce per tutti». Significa che abbiamo rimosso gli ostacoli misurabili. Quando avremo abbastanza dati reali, conteranno quelli sul campo raccolti da Chrome.

Queste tre correzioni hanno una cosa in comune: non chiedono più lavoro. Hanno tolto lavoro fatto nel momento sbagliato, o lavoro che non andava fatto.

Domande frequenti

Cosa intende Lighthouse per «reflow forzato»?

JavaScript può costringere il browser a calcolare troppo presto le modifiche agli stili. Di solito il browser le raccoglie e le applica insieme. Se nel mezzo il codice legge una misura, deve elaborarle subito. offsetWidth, getBoundingClientRect, scrollY e getComputedStyle tutte le letture lo attivano.

Conviene sempre incorporare il CSS nella pagina?

No. Un file separato alla seconda visita arriva dalla cache, ed è un vantaggio. Incorporarlo accelera la prima visita e rallenta quelle successive. Devi decidere in base al sito: se pesa molto il traffico dai risultati di ricerca, incorporare conviene. Noi lo abbiamo misurato. Conveniva.

Devo smettere di usare content-visibility?

No. Rimandare il rendering delle sezioni fuori schermo accelera davvero la prima visualizzazione. Il problema è lo strumento, che non riesce a leggere il colore delle aree rinviate. Basta disattivare la funzione solo dove genera falsi avvisi.

100 punti significano davvero che il sito è veloce?

No. È un test di laboratorio basato su un dispositivo e una rete prestabiliti. Misurando più volte la stessa pagina, il punteggio oscilla tra 98 e 100. Il dato reale compare nelle rilevazioni sul campo di Chrome, che richiedono tempo per popolarsi.

Perché misurare tutte le pagine invece di usare un campione?

Da noi, 13 pagine su 150 non sono mai entrate nel campione e 11 erano nella stessa lingua. La home stava a 100, le pagine arabe ferme a 83. Non puoi dire nulla su una pagina che non misuri.

Fonti

Lavori collegati