Due pagine con lo stesso peso possono apparire sullo schermo in tempi molto diversi. Spesso la differenza non sta in quanti kilobyte vengono scaricati, ma in quando e in che ordine il browser li riceve e li elabora. Capire come il browser costruisce una pagina permette di spostare poche righe nell'HTML e guadagnare secondi percepiti.
Come il browser costruisce la pagina
In modo semplificato, il browser:
- scarica l'HTML e inizia ad analizzarlo dall'alto verso il basso, costruendo il DOM;
- quando incontra un foglio di stile, lo scarica e costruisce il CSSOM (il modello degli stili);
- quando incontra uno script, lo scarica e lo esegue;
- combina DOM e CSSOM, calcola il layout e disegna i pixel.
Due tipi di risorse possono fermare questo processo: il CSS e gli script sincroni.
Il CSS blocca il rendering
Il browser non disegna la pagina finché non ha i fogli di stile caricati nell'head. È un comportamento voluto: senza, l'utente vedrebbe per un istante la pagina senza stile e poi un salto improvviso. La conseguenza è che un CSS grande o lento ritarda la comparsa di tutto il contenuto.
Cosa puoi fare:
- riduci il CSS iniziale: rimuovi le regole inutilizzate e suddividi gli stili per sezione o tipo di pagina;
- usa gli attributi
media: un foglio conmedia="print"non blocca il rendering dello schermo; - valuta il CSS critico: inserisci direttamente nell'HTML le poche regole necessarie alla parte visibile senza scorrere, e carica il resto in seguito. È una tecnica efficace ma complessa da mantenere, da riservare ai casi in cui serve davvero.
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/print.css" media="print">
Gli script sincroni bloccano l'analisi
Uno script classico, senza attributi, ferma l'analisi dell'HTML finché non è stato scaricato ed eseguito:
<script src="/js/app.js"></script>
Il motivo storico è che lo script potrebbe modificare il documento con document.write, quindi il browser non può proseguire. Se lo script è nell'head e pesa molto, l'utente fissa una pagina bianca.
defer
<script src="/js/app.js" defer></script>
Con defer lo script viene scaricato in parallelo all'analisi dell'HTML ed eseguito solo alla fine, prima dell'evento DOMContentLoaded. Gli script con defer vengono eseguiti nell'ordine in cui compaiono. È la scelta giusta per quasi tutti gli script dell'applicazione, che hanno bisogno del DOM completo e spesso dipendono l'uno dall'altro.
async
<script src="https://esempio-analytics.it/tag.js" async></script>
Con async lo script viene scaricato in parallelo ed eseguito appena è pronto, interrompendo l'analisi in quel momento. L'ordine di esecuzione non è garantito. È adatto a script indipendenti, che non dipendono da altri e da cui nessuno dipende, come alcuni strumenti di misurazione.
Riepilogo
| Attributo | Download | Esecuzione | Ordine |
|---|---|---|---|
| nessuno | blocca l'analisi | subito | sì |
defer |
in parallelo | dopo l'analisi dell'HTML | sì |
async |
in parallelo | appena scaricato | no |
type="module" |
in parallelo | come defer |
sì |
Gli script di tipo modulo sono differiti per impostazione predefinita; puoi aggiungere async anche a un modulo se vuoi che venga eseguito appena pronto.
Suggerimenti di priorità: preload, preconnect e fetchpriority
Il browser scopre le risorse man mano che analizza l'HTML e i CSS. Alcune risorse importanti, però, vengono scoperte tardi: un font citato dentro un CSS, un'immagine di sfondo, uno script caricato da un altro script. I resource hints permettono di anticiparle.
preload
Chiede di scaricare subito una risorsa che sarà sicuramente necessaria:
<link rel="preload" href="/fonts/testo.woff2" as="font" type="font/woff2" crossorigin>
L'attributo as è obbligatorio perché il browser assegni la priorità corretta. Per i font serve crossorigin anche se il file è sullo stesso dominio. Non abusarne: tutto ciò che precarichi compete per la banda con il resto, e precaricare risorse che poi non vengono usate produce un avviso nella console e uno spreco.
preconnect
Apre in anticipo la connessione verso un altro dominio da cui caricherai risorse a breve, risparmiando il tempo di risoluzione del nome e di negoziazione della connessione sicura:
<link rel="preconnect" href="https://cdn.esempio.it">
Limitalo a uno o due domini davvero critici; per gli altri esiste dns-prefetch, più leggero.
fetchpriority
Indica la priorità relativa di una risorsa rispetto alle altre dello stesso tipo:
<img src="/img/copertina.webp" fetchpriority="high" alt="...">
<script src="/js/widget-chat.js" async fetchpriority="low"></script>
Gli script di terze parti
Widget di chat, pulsanti social, mappe, strumenti pubblicitari e di tracciamento sono spesso la causa principale di pagine lente, e sfuggono al tuo controllo. Qualche strategia:
- carica solo ciò che serve davvero e rivedi periodicamente l'elenco;
- usa sempre
asyncodefer; - per componenti pesanti come video o mappe, mostra un'anteprima statica e carica il componente vero solo al clic;
- valuta l'impatto con il pannello Prestazioni degli strumenti per sviluppatori, bloccando temporaneamente un dominio per vedere quanto cambia.
Come individuare le risorse bloccanti
Lighthouse segnala le risorse che bloccano il rendering con una voce dedicata. Nel pannello Rete puoi osservare la cascata delle richieste: una lunga catena in cui ogni risorsa inizia solo quando la precedente è finita indica una scoperta tardiva, risolvibile con preload o spostando i riferimenti nell'HTML.
In sintesi
- Il CSS nell'
headblocca il rendering: mantienilo piccolo e usamediaper gli stili non essenziali. - Gli script senza attributi bloccano l'analisi dell'HTML: usa
deferper gli script dell'applicazione. asyncè adatto solo a script indipendenti, perché l'ordine non è garantito.preload,preconnectefetchpriorityanticipano le risorse critiche, se usati con parsimonia.- Gli script di terze parti vanno ridotti, caricati in modo asincrono o solo su richiesta.