Quando il benchmark non vede lo stesso sito

100 su PageSpeed Insights, 77 in Chrome sulla stessa pagina: come LiteSpeed Cache riconosce i tester e serve loro una variante diversa da quella degli utenti reali.

La stessa pagina misurata due volte: 100 su PageSpeed Insights, dove il JavaScript resta in attesa, e 77 per l’utente reale in Chrome, con LCP a 4,5 secondi

Un benchmark ha senso solo se misura lo stesso oggetto che useranno le persone. Se il software sotto esame riesce a capire di essere misurato e cambia comportamento, il risultato non descrive più il sito: descrive il sito in condizioni di esame. È quello che accade su molti siti WordPress con LiteSpeed Cache quando è attiva la funzione Guest Optimization, e lo si può dimostrare leggendo il codice sorgente del plugin.

Due test, due siti diversi

Il punto di partenza è un sito WordPress su LiteSpeed Web Server con LiteSpeed Cache (LSCache), testato con il profilo mobile di Lighthouse: un Moto G Power emulato su una connessione 4G lenta, cioè un banco prova volutamente severo. Su PageSpeed Insights la pagina ottiene un punteggio perfetto.

Risultato di Google PageSpeed Insights da mobile: prestazioni 100, FCP 1,1 s, LCP 1,3 s, Total Blocking Time 0 ms, CLS 0
PageSpeed Insights, profilo mobile: 100 su 100 e Total Blocking Time a zero.

La stessa pagina, analizzata pochi istanti dopo con Lighthouse direttamente dai DevTools di Chrome e con lo stesso profilo mobile, racconta un’altra storia.

Lighthouse eseguito dai DevTools di Chrome sulla stessa pagina: performance 77, FCP 3,0 s, LCP 4,5 s, Total Blocking Time 170 ms, CLS 0
Lighthouse in Chrome sulla stessa pagina: 77, con LCP a 4,5 secondi.
Metrica (mobile)PageSpeed InsightsLighthouse in Chrome
Performance10077
First Contentful Paint1,1 s3,0 s
Largest Contentful Paint1,3 s4,5 s
Total Blocking Time0 ms170 ms
Speed Index1,1 s3,0 s
Cumulative Layout Shift00

Due esecuzioni di Lighthouse non danno mai lo stesso numero: CPU, versione del browser e throttling introducono sempre una certa variabilità. Ma una variabilità di qualche punto non spiega un LCP che passa da 4,5 a 1,3 secondi, né 170 millisecondi di blocco del thread principale che diventano zero. A quel punto conviene smettere di guardare il cerchio verde e aprire il codice. Tutti i riferimenti che seguono sono al repository ufficiale litespeedtech/lscache_wp, versione 7.9.1, e chiunque può verificarli.

Non è il normale delay JavaScript

Una premessa necessaria: ritardare il JavaScript non è di per sé scorretto. Defer, delay, minificazione, Critical CSS e lazy loading sono tecniche legittime, usate da molti strumenti di ottimizzazione, e quando si applicano agli utenti reali possono migliorare davvero l’esperienza. Il problema nasce quando il software distingue chi lo sta misurando e riserva al tester una versione della pagina diversa da quella che riceverà il browser di un visitatore. È la differenza tra ottimizzare un sito e ottimizzare il risultato di un esame.

Come il plugin riconosce chi lo misura

Il riconoscimento avviene su due livelli. Il file data/gm_uas.txt contiene la lista degli User-Agent da trattare sempre come “Guest”:

Lighthouse
GTmetrix
Google
Pingdom
bot
spider
PTST
HeadlessChrome

Le voci vengono unite in un’espressione regolare che non distingue maiuscole e minuscole, quindi basta che una di queste parole compaia nello User-Agent. La sola voce Google intercetta sia PageSpeed Insights sia Googlebot. Accanto c’è data/gm_ips.txt, con i blocchi di indirizzi da riconoscere a prescindere dallo User-Agent: tra i primi compaiono range di Google, compreso il blocco storico di Googlebot, e due blocchi di GTmetrix. Il riconoscimento è quindi doppio: cambiare User-Agent non basta a sfuggirgli.

Dalla versione 7.7 c’è un ulteriore cambiamento. Le due liste, prima modificabili dal pannello del plugin, vengono scaricate ogni giorno dalle API di QUIC.cloud e hanno la precedenza sui file inclusi nel plugin, mentre le opzioni corrispondenti vengono rimosse dal database durante l’aggiornamento. In pratica, l’elenco di chi riceve la variante per i tester non lo decide più chi amministra il sito, ma il produttore del plugin, che può aggiornarlo in qualsiasi momento su tutte le installazioni.

Due cancelli: chi entra in Guest Mode e chi ne esce

Il meccanismo ha due punti di decisione distinti. Il primo è dentro WordPress, nel metodo _maybe_guest_mode(), e non guarda né User-Agent né IP: controlla soltanto la presenza del cookie _lscache_vary. Chiunque arrivi senza quel cookie, persona o robot, riceve la variante Guest.

Il secondo decide chi riceve il cookie, cioè chi esce dalla variante Guest. In fondo alla pagina Guest il plugin inserisce un piccolo script che chiama guest.vary.php. Lì il metodo update_guest_vary() contiene il passaggio decisivo:

if ( $this->always_guest() ) {
	echo '[]';
	exit;
}

Se la richiesta appartiene a un tester riconosciuto, non riceve il cookie e non viene invitata a ricaricare la pagina. Tutti gli altri ricevono il cookie e una risposta che ordina al browser di ricaricare la pagina, che da quel momento verrà servita nella variante normale. I due percorsi, affiancati, si leggono così:

Percorso di PageSpeed Insights: richiesta, User-Agent o IP riconosciuto, always_guest uguale a true, la richiesta resta Guest, Guest Optimization applicata, JavaScript ritardato in attesa di interazione
Il percorso del tester: resta sulla variante Guest.
Percorso di un browser reale: richiesta iniziale, pagina Guest, chiamata a guest.vary.php, User-Agent e IP non qualificati, cookie e ricaricamento, pagina normale
Il percorso del browser reale: cookie, ricaricamento e variante normale.

PageSpeed Insights parte sempre da un profilo pulito, arriva da un indirizzo Google e dichiara uno User-Agent con Chrome-Lighthouse: viene servito come Guest e lì resta per tutta la durata del test. Il Chrome dello sviluppatore, invece, ha già il cookie o lo riceve alla prima visita: Lighthouse lanciato dai DevTools misura quindi la variante normale, la stessa che vede chiunque abbia già visitato il sito, cioè la maggior parte degli utenti.

Il costo per l’utente reale: la pagina si carica due volte

La Guest Mode viene presentata come il modo per rendere velocissima la prima visita. Ma il primo visitatore reale riceve la pagina Guest, il browser la disegna, lo script contatta guest.vary.php, riceve il cookie e ricarica l’intera pagina, questa volta nella variante normale. Il costo effettivo della prima visita è la somma dei due caricamenti.

Il plugin stesso lo riconosce implicitamente: nell’<head> inserisce uno script che, dopo il ricaricamento, sovrascrive document.referrer con il valore salvato in precedenza. Senza questa correzione gli strumenti di analytics vedrebbero il sito stesso come provenienza di ogni visita e il traffico organico sparirebbe dai report. Il tester, che non ricarica mai, non paga nessuno di questi costi.

Cosa cambia nella variante per i tester

Quando Guest Optimization è attiva viene definita la costante LITESPEED_GUEST_OPTM, e in src/optimize.cls.php la configurazione del JavaScript scelta dall’amministratore viene sostituita:

$this->cfg_js_defer = $this->conf( self::O_OPTM_JS_DEFER );
if ( defined( 'LITESPEED_GUEST_OPTM' ) ) {
	$this->cfg_js_defer = 2;
}

Il valore 2 corrisponde alla modalità delayed, non al semplice defer. Ed è esattamente la situazione creata dai preset ufficiali “Advanced” e “Aggressive”, quelli consigliati alla maggior parte dei siti: Guest Mode e Guest Optimization attive, JavaScript impostato a 1, cioè defer, per tutti gli altri. Un amministratore che applica il preset con un clic ottiene, senza saperlo, due siti: il tester riceve il delay, l’utente il defer.

Il JavaScript è solo la parte più visibile. Nella variante Guest il plugin forza anche minificazione e combinazione di CSS e JavaScript, rimozione dei Google Fonts, lazy load di immagini e iframe, eliminazione dei fallback <noscript> e segnaposto per le immagini. Soprattutto, ignora le esclusioni per URL e per ruolo che l’amministratore ha configurato proprio perché certe ottimizzazioni rompono qualcosa: per il tester vengono applicate comunque. Il tester non apre menu e non compila moduli, quindi non si accorgerà mai di cosa si è rotto.

Script che partono solo se qualcuno interagisce

In modalità delayed un normale tag script viene riscritto così:

<!-- prima -->
<script src="/wp-content/themes/theme/app.js"></script>

<!-- dopo -->
<script data-optimized="1" type="litespeed/javascript"
        data-src="/wp-content/themes/theme/app.js"></script>

litespeed/javascript non è un tipo che il browser esegue e l’indirizzo del file non si trova più in src: la richiesta di rete semplicemente non parte. Lo stesso accade agli script inline e agli iframe, quindi video incorporati, mappe e pubblicità. Nei file JavaScript serviti, inoltre, la stringa DOMContentLoaded viene sostituita con un evento definito dal plugin, emesso solo dopo il caricamento degli script ritardati.

A riattivare tutto è il loader js_delay.js, che resta in ascolto di eventi come mouseover, click, keydown, wheel e touchstart. Nel sorgente c’è anche un timer di sicurezza che avrebbe caricato gli script dopo 70 millisecondi comunque, ma è commentato. Senza interazione, quindi, il JavaScript ritardato non viene mai eseguito. E Lighthouse non muove il mouse né tocca lo schermo: durante la finestra di misurazione il costo di quel codice non entra mai nel calcolo. Il Total Blocking Time a zero non significa che il JavaScript sia diventato gratuito, ma che non è stato eseguito durante il test.

Come verificarlo sul tuo sito

Bastano due richieste. La prima simula il tester, senza cookie e con Lighthouse nello User-Agent; la seconda simula un visitatore di ritorno, con il cookie di variazione:

curl -s -A "Mozilla/5.0 (Linux; Android 11; moto g power (2022)) Chrome-Lighthouse" \
  https://www.iltuosito.it/ | grep -c 'type="litespeed/javascript"'

curl -s -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/128.0.0.0" \
  -b "_lscache_vary=1" https://www.iltuosito.it/ | grep -c 'type="litespeed/javascript"'

Se il primo comando restituisce decine di occorrenze e il secondo zero, le due varianti sono davanti ai tuoi occhi. Per ottenere da PageSpeed Insights un numero che rappresenti gli utenti reali bisogna disattivare Guest Optimization, o applicare a tutti lo stesso livello di ottimizzazione riservato ai tester. Il punteggio scenderà: quello è il numero vero.

Perché è un problema di business

La stessa documentazione di LiteSpeed avverte che Guest Optimization può mascherare i problemi reali e suggerisce di disattivarla per ottenere una valutazione accurata dagli strumenti di test. È un’ammissione importante, ma il danno maggiore è organizzativo. Un 77 genera domande: perché l’LCP è a 4,5 secondi, quale script blocca il thread principale, se il tema è troppo pesante. Un 100 comunica che il lavoro è finito. Il cliente vede 100, l’agenzia vede 100, chi dovrebbe approvare altre ore di ottimizzazione non ne vede il motivo. Il problema resta, ma nessuno lo cerca più.

Intanto i dati che contano continuano a raccontare la verità. I Core Web Vitals usati da Google come segnale di ranking non provengono da Lighthouse, ma dal Chrome UX Report, cioè dai browser degli utenti reali: quelli con il cookie, sulla variante normale. Non a caso sullo stesso report di PageSpeed Insights capita di vedere il 100 del laboratorio accanto a dati sul campo insufficienti. E poiché Google e i range di Googlebot sono nelle liste, anche il crawler riceve la variante con il JavaScript neutralizzato: una questione da valutare con attenzione per i siti che generano contenuti lato client.

Cosa non stiamo dicendo

LiteSpeed Web Server e la sua cache lato server sono strumenti efficaci, al pari della FastCGI cache di Nginx o di Varnish: servire HTML già pronto senza avviare PHP e database è una tecnica eccellente. Allo stesso modo minificazione, Critical CSS, lazy load, defer e delay sono strumenti validi. Quello che contestiamo è una variante della pagina riconoscibile dal tester, che fa osservare al benchmark condizioni diverse da quelle degli utenti. Va aggiunto che, per sfruttare la cache di LiteSpeed Web Server su WordPress, il plugin LSCache è di fatto necessario per l’invalidazione delle pagine: separare il server dal plugin, nella pratica, non è sempre possibile.

In sintesi

Il meccanismo è documentato dal sorgente: liste di User-Agent e IP dei tester aggiornate dal produttore, un cookie negato solo a chi viene riconosciuto, una variante in cui il JavaScript viene forzato in modalità delayed e caricato soltanto dopo un’interazione che durante un audit automatico non avviene. Il risultato è un 100 su PageSpeed Insights e un 77 in Chrome sulla stessa pagina, con un utente reale che alla prima visita paga addirittura due caricamenti.

Il paragone con il Dieselgate è inevitabile, pur con conseguenze non comparabili: anche lì il software riconosceva il banco prova e cambiava comportamento. La nostra regola è semplice: un sito veloce non ha bisogno di sapere chi lo sta testando. Deve esserlo per PageSpeed Insights, per Lighthouse e soprattutto per una persona che apre il sito dal proprio telefono. Quando il banco prova e la strada raccontano storie diverse, crediamo alla strada.

L’analisi completa, con tutti i riferimenti a file e metodi del sorgente, è pubblicata su Managed Server.

IL PROSSIMO PASSO

Partiamo
dal tuo sito.

Raccontaci il progetto, i problemi che riscontri e i tuoi obiettivi. Definiremo insieme un intervento su misura.

Richiedi un’analisi

Ti porta al modulo della pagina Contatti.

MANAGED SERVER S.R.L.info@corewebvitals.it +39 02 5656 9681

Lunedì – venerdì · 09:30 – 19:30

Preferisci un modulo online?
Contattaci su managedserver.it ↗