Indice dei contenuti dell'articolo:
Quando si parla di ottimizzazione delle performance web, prima o poi si arriva sempre alla stessa domanda: meglio inserire il CSS direttamente dentro la pagina HTML oppure caricarlo attraverso uno o più fogli di stile esterni?
La risposta apparentemente più corretta sembrerebbe semplice: mettere il CSS in un file esterno, farlo scaricare una volta al browser e lasciare che venga memorizzato nella cache. Alla prima pagina avremo un piccolo costo aggiuntivo, ma durante la navigazione successiva quel file sarà già disponibile localmente e tutte le altre pagine risulteranno più veloci.
È un ragionamento tecnicamente sensato.
Il problema è che parte da un presupposto che molto spesso non corrisponde al modo in cui gli utenti utilizzano realmente il Web:
che dopo la prima pagina ne esista necessariamente una seconda.
Molte visite iniziano da Google, da un social network, da una newsletter, da un aggregatore o da un link condiviso su WhatsApp. L’utente apre una pagina, legge quello che gli interessa e se ne va.
La “navigazione successiva”, quella per la quale avevamo accettato di rendere leggermente più lento il primo caricamento in cambio del vantaggio della cache, semplicemente non avviene.
Ed è qui che cambia completamente il modo in cui dovremmo ragionare sulle performance.
Il CSS esterno e il suo grande vantaggio: la cache
Il modo classico di includere un foglio di stile in una pagina è questo:
<link rel="stylesheet" href="/css/style.css">
È una soluzione pulita, semplice da mantenere e soprattutto molto efficiente quando lo stesso CSS viene utilizzato su molte pagine.
Il browser scarica style.css e, se le intestazioni HTTP di caching sono configurate correttamente, può conservarlo nella propria cache.
Quando l’utente apre una seconda pagina che utilizza lo stesso file, non è necessariamente necessario trasferire nuovamente tutti quei byte dalla rete.
Per un sito composto da centinaia o migliaia di pagine questo rappresenta un vantaggio notevole.
Un unico foglio di stile può contenere le regole per header, footer, menu, articoli, sidebar, form, pulsanti, tabelle, e-commerce e così via.
Dal punto di vista dello sviluppo è inoltre molto comodo: si modifica un file e la modifica viene propagata a tutto il sito.
Il problema del CSS esterno, tuttavia, è il suo ruolo all’interno del Critical Rendering Path.
Perché un foglio CSS esterno può rallentare il primo rendering
Quando il browser riceve il documento HTML comincia a leggerlo dall’alto verso il basso.
Quando incontra qualcosa del genere:
<head>
<link rel="stylesheet" href="/css/style.css">
</head>
scopre che per visualizzare correttamente la pagina deve recuperare anche style.css.
Il browser può continuare a svolgere altro lavoro, ma il foglio di stile viene normalmente considerato una risorsa render-blocking: prima di dipingere correttamente il contenuto deve scaricare e analizzare il CSS necessario alla costruzione del CSSOM.
Il motivo è perfettamente logico.
Mostrare prima una pagina completamente priva di stile e ridisegnarla mezzo secondo dopo provocherebbe un’esperienza visiva pessima.
Di conseguenza il browser preferisce attendere.
Su una connessione veloce questa attesa può sembrare insignificante.
Ed è proprio qui che nasce uno dei bias più frequenti nello sviluppo web.
Il sito è velocissimo. Sì, dalla fibra dell’ufficio
Chi sviluppa siti web passa gran parte della giornata collegato a reti decisamente migliori rispetto a quelle utilizzate da una parte consistente degli utenti.
Fibra FTTH, connessioni aziendali, Wi-Fi veloce, 5G con ottimo segnale.
In queste condizioni una richiesta aggiuntiva per scaricare 50, 100 o 200 KB di CSS sembra quasi irrilevante, questione di 50 millisecondi, per l’occhio umano tutto quello che è sotto ai 200 millisecondi viene recepito come istantaneo. Usain Bolt ai blocchi di partenza ha un tempo di reazione massimo (record) di 146 millisecondi. Usain Bolt. L’uomo più veloce della terra.
Dalla fibra dell’ufficio insomma o dalla 5G veloce, si apre la pagina e tutto compare immediatamente.
Il problema nasce quando quello stesso sito viene utilizzato in condizioni differenti.
Immaginiamo uno smartphone collegato a una rete 4G con copertura mediocre, magari in una zona montuosa dell’Appennino, dentro un edificio con muri spessi oppure in una località turistica lontana dalle principali infrastrutture di rete.
Personalmente ricordo molto bene situazioni simili durante alcune vacanze estive in Sardegna: bastava spostarsi in determinate zone perché una connessione che sulla carta era 4G diventasse improvvisamente lenta, instabile e con una latenza completamente diversa da quella a cui siamo abituati in città.
Ed è proprio la latenza uno degli aspetti che più facilmente vengono sottovalutati.
Non conta solamente quanti megabit al secondo può teoricamente trasferire la connessione.
Conta quanto tempo passa tra la scoperta della risorsa, la richiesta al server e l’arrivo dei primi dati.
In laboratorio o sulla fibra dell’ufficio tutto può sembrare istantaneo. In mobilità, in condizioni reali, ogni dipendenza critica aggiuntiva può diventare molto più evidente.
Il bias della “prima pagina un po’ più lenta”
Il ragionamento tradizionale è spesso questo:
“Va bene, alla prima visita deve scaricare il CSS. Sarà leggermente più lenta. Poi però il file rimane in cache e tutte le pagine successive saranno velocissime.”
Non è un ragionamento sbagliato dal punto di vista informatico.
È sbagliato quando viene trasformato automaticamente in una descrizione del comportamento degli utenti.
Per moltissime sessioni la prima pagina è anche l’ultima.
Un utente cerca su Google:
“Come configurare Redis su WordPress?”
apre il risultato che gli interessa, legge l’articolo, trova la risposta e chiude la scheda.
Non apre la home page.
Non visita la categoria.
Non consulta altri cinque articoli.
Non arriva mai a quella fantomatica seconda pagina nella quale avremmo recuperato il costo iniziale grazie alla cache.
Lo stesso accade con landing page pubblicitarie, articoli editoriali, schede prodotto arrivate da Google Shopping, pagine condivise sui social, documentazione tecnica e molte altre tipologie di contenuto.
Per questo l’obiettivo non dovrebbe essere soltanto:
“rendere veloce la navigazione all’interno del sito”.
Dovrebbe essere prima di tutto:
“rendere velocissima la pagina che l’utente ha deciso di aprire adesso”.
Allora mettiamo tutto il CSS inline?
A questo punto potrebbe sembrare naturale arrivare alla conclusione opposta.
Se il CSS esterno richiede una risorsa aggiuntiva, inseriamolo direttamente nell’HTML:
<head> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cstyle%3E%0Abody%20%7B%0A%20%20%20%20margin%3A%200%3B%0A%20%20%20%20font-family%3A%20Arial%2C%20sans-serif%3B%0A%7D%0A%0A.header%20%7B%0Abackground%3A%20%23111%3B%0Acolor%3A%20%23fff%3B%0A%7D%0A%0A.article%20%7B%0Amax-width%3A%201200px%3B%0Amargin%3A%200%20auto%3B%0A%7D%20%3C%2Fstyle%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object mce-object-style" width="20" height="20" alt="<style>" /> </head>
In questo modo le regole CSS arrivano insieme all’HTML.
Non è necessario attendere una richiesta separata per ottenere quel foglio di stile.
Sembra perfetto.
Ma anche questa soluzione, portata all’estremo, crea un problema.
Il problema del CSS inline: l’HTML diventa enorme
Un conto è inserire nella pagina pochi kilobyte di CSS.
Un altro è incorporare l’intero stylesheet di un sito moderno.
Su WordPress, WooCommerce, Magento, PrestaShop o siti costruiti con page builder e framework CSS non è difficile ritrovarsi con centinaia di kilobyte di regole.
Se prendiamo tutto quel CSS e lo inseriamo direttamente dentro ogni documento HTML abbiamo eliminato una richiesta esterna, ma abbiamo creato un altro problema:
abbiamo gonfiato enormemente il documento principale.
Una pagina HTML che in origine pesava 60 KB potrebbe ritrovarsi a pesare 250, 300 o 400 KB prima ancora di considerare JavaScript, immagini, font e altre risorse.
E l’HTML è una risorsa particolarmente importante perché è il punto di partenza di tutto il caricamento.
Prima il browser riceve e analizza il documento, prima può scoprire immagini, script, font, preload e tutte le altre dipendenze.
Riempire il documento con centinaia di kilobyte di CSS significa quindi aumentare la quantità di dati che devono arrivare nella fase più delicata del caricamento.
E perdiamo anche uno dei vantaggi principali della cache
C’è poi un’altra differenza sostanziale.
Un file come:
<link rel="stylesheet" href="/css/style.min.css">
può essere memorizzato separatamente dal browser e riutilizzato in centinaia di pagine.
Se invece quegli stessi 150 KB di CSS vengono inseriti direttamente nell’HTML, vengono incorporati in ogni documento.
Pagina A contiene quei 150 KB.
Pagina B contiene nuovamente quei 150 KB.
Pagina C contiene ancora quei 150 KB.
Il CSS inline non può beneficiare della cache come risorsa autonoma.
Naturalmente il documento HTML stesso può essere sottoposto a caching in varie forme, ma non abbiamo più quella separazione elegante nella quale una risorsa condivisa viene scaricata una volta e riutilizzata attraverso URL differenti.
Siamo quindi davanti a un classico compromesso.
CSS esterno contro CSS inline: entrambi hanno ragione e entrambi hanno torto
Il CSS esterno offre:
- ottima riusabilità;
- cache indipendente dal documento HTML;
- manutenzione più semplice;
- HTML più leggero;
- un unico file condivisibile tra molte pagine.
Ma può introdurre una dipendenza di rete render-blocking proprio nel momento più importante: il primo caricamento.
Il CSS inline elimina invece quella richiesta per gli stili incorporati e rende immediatamente disponibili al browser le regole presenti nel documento.
Ma se ne inseriamo troppo:
- gonfiamo l’HTML;
- ripetiamo le stesse regole su pagine differenti;
- perdiamo parte dei vantaggi della cache del CSS esterno;
- aumentiamo il lavoro di trasferimento e parsing del documento;
- rendiamo più difficile la gestione e l’aggiornamento del codice.
La soluzione, come spesso succede nell’ottimizzazione delle performance, non sta in uno dei due estremi.
La soluzione si chiama Critical CSS
Il Critical CSS parte da una domanda molto semplice:
quanto CSS serve realmente per mostrare immediatamente ciò che l’utente vede appena apre la pagina?
Non quanto CSS serve all’intero sito.
Non quanto CSS serve al footer che si trova 6.000 pixel più in basso.
Non quanto CSS serve alla finestra modale che forse verrà aperta.
Non quanto CSS serve alla pagina del checkout mentre stiamo leggendo un articolo.
Soltanto quello necessario per costruire correttamente il primo viewport, il cosiddetto contenuto above the fold.
Quella piccola quantità di CSS viene inserita direttamente nel documento HTML:
<head> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cstyle%3E%0A%2F*%20Critical%20CSS%20*%2F%0Abody%20%7B%0A%20%20%20%20margin%3A%200%3B%0A%20%20%20%20font-family%3A%20system-ui%2C%20sans-serif%3B%0A%7D%0A%0A.site-header%20%7B%0Aheight%3A%2080px%3B%0Abackground%3A%20%23fff%3B%0A%7D%0A%0A.hero%20%7B%0Amax-width%3A%201200px%3B%0Amargin%3A%200%20auto%3B%0A%7D%0A%0A.article-title%20%7B%0Afont-size%3A%2042px%3B%0Aline-height%3A%201.1%3B%0A%7D%20%3C%2Fstyle%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object mce-object-style" width="20" height="20" alt="<style>" /> </head>
A questo punto il browser ha già tutto ciò che gli serve per visualizzare immediatamente la parte iniziale della pagina.
Il resto del CSS continua a vivere in uno stylesheet esterno.
È questa la differenza fondamentale.
Non stiamo trasformando tutto il CSS in inline CSS. Stiamo mettendo inline esclusivamente il CSS critico.
Il resto del CSS può arrivare dopo
Dopo aver fornito immediatamente gli stili indispensabili per il primo rendering, il foglio completo può continuare a essere scaricato separatamente.
Concettualmente l’architettura diventa:
Il browser può quindi mostrare rapidamente header, titolo, hero, immagine principale e contenuto iniziale, mentre il CSS non critico viene recuperato per completare il resto della pagina.
In determinate implementazioni il foglio non critico può essere caricato in maniera non bloccante, per esempio attraverso strategie di preload e successiva applicazione dello stylesheet.
Una tecnica storicamente utilizzata è concettualmente simile a questa:
<link rel="preload"
href="/css/style.min.css"
as="style"
onload="this.onload=null;this.rel='stylesheet'">
<noscript>
<link rel="stylesheet" href="/css/style.min.css">
</noscript>
Non significa che questa precisa implementazione debba essere utilizzata indistintamente su ogni sito: il caricamento asincrono del CSS va progettato e soprattutto testato, perché esistono implicazioni legate a Content Security Policy, compatibilità, priorità delle risorse e possibili fenomeni di FOUC.
Il principio, però, rimane valido: rendere immediatamente disponibile ciò che serve subito e rimandare ciò che servirà dopo.
Critical CSS non significa semplicemente “CSS piccolo”
Un errore frequente è pensare che basti prendere le prime 200 righe dello stylesheet.
Non funziona così.
Il Critical CSS dipende dal contenuto realmente presente nel primo viewport.
Una homepage può aver bisogno degli stili dell’hero.
Una pagina articolo degli stili di titolo, autore, breadcrumb e immagine featured.
Una scheda prodotto di prezzo, galleria, disponibilità e pulsante di acquisto.
Il CSS critico ideale può quindi essere differente tra template diversi.
Inoltre va considerato il responsive design.
Ciò che compare above the fold su un monitor desktop 1920×1080 non coincide necessariamente con quello che appare su uno smartphone largo 390 pixel.
Per questo una buona implementazione del Critical CSS dovrebbe essere automatizzata all’interno del processo di build, caching o ottimizzazione, anziché essere mantenuta manualmente riga per riga.
Il Critical CSS non deve diventare un altro mostro inline
Esiste però una trappola.
Alcuni sistemi che promettono di generare Critical CSS finiscono per inserire nel documento quantità enormi di CSS “per sicurezza”.
Se il Critical CSS arriva a 100, 150 o 200 KB abbiamo perso gran parte del vantaggio.
Lo scopo è esattamente opposto:
il CSS inline deve contenere il minimo indispensabile per dipingere correttamente il primo contenuto significativo.
Tutto il resto dovrebbe rimanere fuori dal percorso critico.
Occorre quindi misurare.
Chrome DevTools, Coverage, Performance panel e test effettuati con network throttling sono molto più utili della semplice sensazione che “sul mio computer apre subito”.
Testate il sito come lo vedrebbe un utente con una connessione mediocre
Questo è probabilmente il consiglio più importante.
Se sviluppate un sito collegati a una FTTH da centinaia di megabit, non utilizzate la vostra esperienza di navigazione come benchmark.
Simulate latenze elevate.
Limitate la banda.
Provate CPU meno potenti.
Guardate cosa succede quando ogni richiesta di rete smette di sembrare gratuita.
È proprio in queste condizioni che il Critical Rendering Path diventa evidente.
Un file CSS esterno che dalla vostra scrivania arriva praticamente all’istante può diventare il motivo per cui un utente resta davanti a uno schermo bianco o a un contenuto incompleto per centinaia di millisecondi in più.
Su una visita che dura una sola pagina, quei millisecondi non verranno mai “recuperati” dalla cache durante la navigazione successiva.
La soluzione non è scegliere tra inline ed external CSS
La vera domanda, quindi, non dovrebbe essere:
“CSS inline o CSS esterno?”
È una domanda troppo semplice per un problema che riguarda priorità e tempi.
La domanda corretta è:
“quale CSS serve adesso e quale CSS può arrivare dopo?”
Il CSS necessario immediatamente deve essere piccolo, mirato e disponibile senza introdurre ulteriori dipendenze critiche.
Il CSS globale, riutilizzabile e meno urgente può invece rimanere esterno, beneficiare della cache e servire il resto della pagina e della navigazione.
Conclusioni: ottimizzate la prima pagina come se fosse anche l’ultima
Per anni abbiamo ragionato sulle performance web immaginando una sequenza abbastanza lineare:
prima pagina leggermente più lenta, download delle risorse condivise, cache, navigazione successiva velocissima.
Dal punto di vista tecnico è un modello perfettamente valido.
Dal punto di vista dell’utente moderno, però, è spesso incompleto.
La prima pagina può essere l’unica pagina che quell’utente visiterà.
Non possiamo quindi sacrificare eccessivamente il caricamento iniziale nella speranza di recuperare il costo su una seconda navigazione che potrebbe non avvenire mai.
Allo stesso tempo, mettere centinaia di kilobyte di CSS direttamente dentro ogni documento HTML non è la soluzione: significa trasferire ripetutamente regole inutili, gonfiare il documento e rinunciare a uno dei principali benefici dei fogli di stile esterni, cioè la possibilità di essere memorizzati e riutilizzati dalla cache.
Il Critical CSS rappresenta un compromesso molto più intelligente.
Pochissimo CSS inline per visualizzare immediatamente ciò che conta. CSS esterno per tutto il resto.
È un approccio che non ottimizza soltanto il numero delle richieste.
Ottimizza soprattutto il momento nel quale quelle risorse diventano necessarie.
Ed è questa, in fondo, una delle regole più importanti delle performance web moderne:
non bisogna necessariamente scaricare meno. Bisogna soprattutto evitare di aspettare ciò che non serve ancora.
Perché l’utente non ci giudica sulla velocità teorica della quinta pagina visitata.
Ci giudica nel momento esatto in cui tocca un risultato su Google, apre il nostro sito e aspetta che sullo schermo compaia qualcosa.
E in quel momento abbiamo una sola possibilità per sembrare veloci: esserlo subito.





