Indice dei contenuti dell'articolo:
Quando un sito WordPress o WooCommerce cresce in traffico, complessità e numero di richieste simultanee, arriva inevitabilmente un momento in cui non basta più “ottimizzare PHP”. Si possono migliorare query SQL, ridurre plugin inutili, usare object cache, aggiornare PHP-FPM, configurare OPcache e alleggerire il tema. Tutto utile, certamente. Ma se ogni visita continua a generare una nuova esecuzione completa di WordPress, con bootstrap del core, caricamento dei plugin, query al database, rendering del tema e produzione dell’HTML, il limite architetturale rimane.
Una Web Cache nasce per risolvere esattamente questo problema: evitare che il backend applicativo debba rigenerare la stessa risposta quando quella risposta è uguale per molti utenti. In altre parole, se la homepage pubblica di un sito è identica per migliaia di visitatori anonimi, non ha senso eseguire PHP e interrogare MySQL migliaia di volte. È molto più efficiente generare quella pagina una volta, conservarla in cache e servirla direttamente dalla cache fino a quando non scade o viene invalidata.
Questo approccio non è un trucco marginale, ma un cambio di prospettiva. Lo sviluppatore non deve più ragionare soltanto in termini di “questa funzione stampa un dato”, ma deve chiedersi: questo dato può essere condiviso tra più utenti? Se sì, può stare nell’HTML cacheabile. Se no, deve essere gestito in modo diverso: tramite JavaScript, chiamate AJAX/REST, Edge Side Include, endpoint non cacheati, cookie controllati o logiche applicative separate.
La cache HTTP è uno degli strumenti più potenti per ridurre TTFB, carico CPU, query al database e saturazione del backend. Tuttavia, è anche uno degli strumenti più facili da rompere con una riga di codice sbagliata. Un cookie impostato senza criterio, un header Cache-Control: no-store su una pagina pubblica, una sessione PHP avviata ovunque, una personalizzazione server-side per singolo utente o una variante mobile/desktop gestita male possono trasformare una piattaforma performante in un sistema che bypassa costantemente la cache.
Questa guida è pensata per lo sviluppatore che lavora su WordPress e WooCommerce in presenza di Varnish Cache o di una web cache generica, incluse configurazioni basate su NGINX reverse proxy, CDN o cache HTTP a livello hosting.
Cos’è una Web Cache
Una Web Cache è un componente intermedio che conserva temporaneamente una risposta HTTP e la riutilizza per richieste successive compatibili. Può trovarsi nel browser dell’utente, in una CDN, in un reverse proxy davanti al web server, oppure direttamente nel web server tramite moduli come proxy_cache o fastcgi_cache di NGINX.
Nel contesto hosting e sistemistico, quando si parla di Web Cache si intende spesso una cache condivisa server-side, cioè una cache usata da più utenti e posizionata davanti all’applicazione. Il flusso tipico è questo:
- il browser richiede una pagina;
- la richiesta arriva alla cache;
- se la cache ha una copia valida della risposta, la serve immediatamente;
- se non la possiede o se la copia è scaduta, inoltra la richiesta al backend;
- il backend genera la risposta e la cache decide se conservarla.
In una configurazione classica con Varnish, il percorso può essere:
Browser → Varnish → NGINX/Apache → PHP-FPM → WordPress → MySQL
Con una cache hit, invece, il percorso si ferma molto prima:
Browser → Varnish → risposta HTML già pronta
Questa differenza è enorme. Una risposta servita da Varnish può evitare completamente l’esecuzione di PHP, il caricamento di WordPress e la connessione al database. Per questo, su pagine pubbliche ad alto traffico, una cache HTTP ben configurata può fare la differenza tra un sito stabile e uno che collassa sotto carico.
Perché Varnish Cache è una scelta enterprise
Varnish Cache è uno dei reverse proxy HTTP più utilizzati quando l’obiettivo è servire contenuti cacheati con alte prestazioni e con una logica di controllo molto precisa. La sua forza non è soltanto la velocità, ma la possibilità di descrivere il comportamento della cache tramite VCL, il linguaggio di configurazione di Varnish.
Con Varnish si può decidere, ad esempio, quali URL non devono mai essere cacheati, quali cookie devono essere ignorati, quali header devono entrare nella cache key, quanto tempo una risposta può restare valida, quando servire contenuto stale, come gestire un purge e come differenziare varianti desktop/mobile, lingua, valuta o paese.
In ambienti enterprise, questa programmabilità è fondamentale. Un sito WooCommerce non ha le stesse esigenze di un magazine, un portale editoriale non ha le stesse regole di una piattaforma membership, e una landing page pubblicitaria non ha lo stesso grado di dinamicità di un’area cliente. Una cache efficace non può essere un interruttore “on/off”: deve essere una politica applicativa coerente.
Varnish è particolarmente adatto quando si vuole separare il compito di servire contenuti pubblici dal compito di generare contenuti dinamici. Il backend WordPress resta responsabile della produzione del contenuto, mentre Varnish diventa il livello di distribuzione veloce, stabile e controllabile.
Il contratto tra applicazione e cache
Una Web Cache non deve essere vista come un elemento esterno e misterioso. Deve essere considerata parte dell’architettura applicativa. Il backend comunica con la cache tramite URL, metodo HTTP, header, cookie e status code. La cache, a sua volta, decide se una risposta è memorizzabile, per quanto tempo e per quali richieste può essere riutilizzata.
Per uno sviluppatore il concetto chiave è questo: ogni pagina deve dichiarare chiaramente se è pubblica, privata, condivisibile, variabile o non cacheabile.
Una pagina pubblica, come un articolo del blog o una categoria prodotto senza personalizzazioni per utente, può usare header simili:
Cache-Control: public, s-maxage=3600, max-age=300
Questo significa: la cache condivisa può conservare la pagina per un’ora, mentre il browser può conservarla per cinque minuti. Al contrario, una pagina privata, come il checkout, l’area account o una dashboard personale, dovrebbe comunicare chiaramente che non è adatta a una cache condivisa:
Cache-Control: private, no-store
Il problema nasce quando l’applicazione non fa distinzione. Se tutte le pagine inviano cookie, se tutte aprono sessioni, se tutte includono dati personali, la cache non può più distinguere ciò che è pubblico da ciò che è privato. A quel punto, per sicurezza, tenderà a bypassare o a non conservare nulla.
Cosa succede a PHP su una pagina cacheata
Uno degli errori più comuni nello sviluppo WordPress in presenza di Varnish è pensare che il codice PHP venga eseguito comunque a ogni visita. Non è così. Se una pagina è servita dalla cache, PHP non viene eseguito. Il visitatore riceve l’HTML generato in precedenza.
Questo significa che qualsiasi logica PHP inserita nel template, nel tema o in uno shortcode viene “fotografata” al momento della generazione della pagina. Se il primo visitatore anonimo genera una pagina con un certo output, quell’output può essere servito anche ai visitatori successivi fino alla scadenza o al purge.
Questo è perfetto per contenuti pubblici come titolo, testo, immagini, prezzo standard di un prodotto, descrizione, breadcrumb, schema markup, menu statico o blocchi editoriali. È invece pericoloso per tutto ciò che cambia per utente, sessione, geolocalizzazione, carrello, login, preferenze personali o cronologia di navigazione.
Esempio sbagliato in un tema WordPress:
<?php
if ( is_user_logged_in() ) {
echo 'Ciao ' . esc_html( wp_get_current_user()->display_name );
} else {
echo 'Accedi al tuo account';
}
?>
Se questa logica viene stampata dentro una pagina cacheabile, si crea una situazione ambigua. O la cache viene bypassata per tutti gli utenti con cookie di login, riducendo l’efficacia del sistema, oppure si rischia di memorizzare una versione non corretta della pagina. Il problema non è la funzione is_user_logged_in() in sé, ma il fatto che venga usata per produrre HTML personalizzato dentro una risposta che dovrebbe essere pubblica.
La soluzione corretta è separare l’HTML pubblico dalla personalizzazione privata. Il template può stampare un contenitore neutro:
<div id="user-area" data-endpoint="/wp-json/ms/v1/user-bar">
<a href="/my-account/">Accedi al tuo account</a>
</div>
Poi JavaScript può interrogare un endpoint non cacheato o cacheato privatamente e aggiornare solo quel frammento:
fetch('/wp-json/ms/v1/user-bar', {
credentials: 'same-origin',
headers: {
'Accept': 'application/json'
}
})
.then(response => response.ok ? response.json() : null)
.then(data => {
if (!data || !data.logged_in) return;
```
const box = document.getElementById('user-area');
if (box) {
box.innerHTML = '<a href="/my-account/">Ciao ' + data.name + '</a>';
}
```
});
In questo modo la pagina principale resta cacheabile, mentre il dettaglio personale viene caricato a parte solo quando serve.
La regola d’oro: non mettere dati privati nell’HTML cacheabile
In presenza di Web Cache, la regola più importante è semplice: tutto ciò che è diverso per singolo utente non deve essere generato server-side dentro una pagina pubblica cacheata.
Questo include:
- nome dell’utente loggato;
- numero di prodotti nel carrello;
- prezzi personalizzati per gruppo cliente;
- sconti individuali;
- messaggi basati su sessione;
- wishlist personale;
- prodotti visti di recente;
- notifiche private;
- contenuti basati su cookie non normalizzati;
- contenuti basati su IP o GeoIP non controllato.
La cache funziona al meglio quando l’HTML è pubblico, deterministico e condivisibile. Se una pagina pubblica contiene cinque piccoli elementi dinamici, non significa che bisogna rinunciare alla cache dell’intera pagina. Significa che bisogna progettare quei cinque elementi come frammenti dinamici separati.
Cookie: il motivo più frequente per cui la cache viene bypassata
I cookie sono probabilmente il principale punto di frizione tra sviluppo applicativo e cache HTTP. Molti framework, CMS, plugin di marketing, sistemi di tracciamento, banner privacy, A/B testing e componenti eCommerce impostano cookie anche quando non sarebbe necessario. Dal punto di vista della cache, però, un cookie nella richiesta può significare: “attenzione, questa risposta potrebbe essere personalizzata”.
In Varnish, una configurazione conservativa tende a non cacheare o a non servire da cache richieste che presentano cookie rilevanti. Questo comportamento è sensato: se il cookie contiene una sessione utente, un carrello o un token di autenticazione, servire una pagina condivisa potrebbe essere sbagliato. Il problema è che spesso i cookie presenti nella richiesta non hanno alcun impatto sul contenuto HTML.
Esempi di cookie che normalmente non dovrebbero invalidare la cache della pagina pubblica:
- cookie di analytics;
- cookie di advertising;
- cookie di consenso già gestiti lato browser;
- cookie di preferenza grafica non usati dal backend;
- cookie di campagne UTM salvati solo per tracking;
- cookie tecnici non collegati al rendering HTML.
Esempi di cookie che invece possono richiedere bypass o variazione:
wordpress_logged_in_*;wp_woocommerce_session_*;woocommerce_items_in_cart;woocommerce_cart_hash;- cookie di membership;
- cookie di valuta se il prezzo cambia server-side;
- cookie di lingua se la lingua non è già nella URL;
- cookie di gruppo prezzo o listino cliente.
Lo sviluppatore dovrebbe quindi evitare di usare cookie come archivio generico di qualsiasi informazione. Ogni cookie deve avere uno scopo chiaro: serve davvero al backend per generare HTML diverso? Se la risposta è no, probabilmente non dovrebbe entrare nella decisione di cache.
Come usare male i cookie in WordPress e WooCommerce
Un errore frequente è impostare un cookie PHP in tutte le pagine del sito, magari nel file functions.php, in un plugin custom o in un mu-plugin:
add_action('init', function () {
setcookie('visited_site', '1', time() + 3600, COOKIEPATH, COOKIE_DOMAIN);
});
Questo codice sembra innocuo, ma comunica alla cache che ogni risposta potrebbe impostare un cookie. Se il backend restituisce un header Set-Cookie, molte configurazioni non memorizzano la risposta per evitare di condividere cookie tra utenti. Il risultato è che anche pagine perfettamente pubbliche possono diventare non cacheabili.
Altro errore comune:
add_action('init', function () {
if (!session_id()) {
session_start();
}
});
Avviare una sessione PHP su tutto WordPress è quasi sempre una pessima idea in presenza di cache full page. La sessione introduce uno stato individuale, imposta cookie e rende la pagina meno condivisibile. In molti casi la sessione viene usata solo per mostrare un messaggio, salvare una preferenza temporanea o ricordare un valore che potrebbe essere gestito con JavaScript, localStorage, transient server-side o endpoint dedicato.
In WooCommerce, i cookie del carrello sono necessari, ma non devono trasformarsi in un bypass globale di tutto il sito. Una configurazione corretta deve escludere checkout, cart, my-account e azioni di carrello, ma deve permettere la cache delle schede prodotto, delle categorie e delle pagine editoriali quando non ci sono condizioni private.
WooCommerce: cosa deve rimanere dinamico
WooCommerce introduce esigenze particolari perché un eCommerce ha sia pagine pubbliche sia pagine fortemente personali. La scheda prodotto, in molti casi, è pubblica. Il carrello, il checkout e l’area account sono invece personali e devono essere esclusi dalla full page cache.
Le pagine che normalmente non devono essere cacheate sono:
- Cart, perché mostra contenuti legati alla sessione del cliente;
- Checkout, perché contiene dati, metodi di spedizione, pagamento, nonce e stato ordine;
- My Account, perché è area privata;
- endpoint WooCommerce, come chiamate
wc-ajaxo API operative; - URL con azioni come
?add-to-cart=, coupon, rimozione prodotti e aggiornamento quantità.
Al contrario, possono spesso essere cacheate:
- homepage pubblica;
- pagine categoria prodotto;
- schede prodotto per utenti anonimi;
- articoli del blog;
- landing page;
- pagine CMS statiche;
- asset statici e immagini.
Attenzione però a prezzo, stock e promozioni. Se il prezzo cambia in base a utente, paese, valuta, ruolo, quantità o listino B2B, la pagina prodotto non è più una semplice pagina pubblica unica. Bisogna decidere se differenziare la cache, caricare il prezzo via JavaScript, usare endpoint dedicati o escludere dalla cache solo le condizioni realmente dinamiche.
VCL di esempio per WordPress e WooCommerce
Una VCL reale va sempre adattata all’infrastruttura, alla versione di Varnish, alla configurazione degli header e alle esigenze del sito. Tuttavia, questo esempio mostra il principio generale: passare le aree realmente dinamiche, pulire i cookie inutili e lasciare cacheabili le pagine pubbliche.
sub vcl_recv {
# Non cacheare metodi non sicuri o non idempotenti
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
```
# WordPress admin e login sempre pass
if (req.url ~ "^/wp-admin" || req.url ~ "^/wp-login.php") {
return (pass);
}
# WooCommerce: aree dinamiche
if (req.url ~ "^/(cart|checkout|my-account)(/|$)") {
return (pass);
}
# WooCommerce: azioni e AJAX operativi
if (req.url ~ "(\?add-to-cart=|wc-ajax=|remove_item=|apply_coupon=)") {
return (pass);
}
# Utenti WordPress loggati: pass
if (req.http.Cookie ~ "wordpress_logged_in_") {
return (pass);
}
# Carrello WooCommerce presente: pass o gestione dedicata
if (req.http.Cookie ~ "woocommerce_items_in_cart=1") {
return (pass);
}
# Rimuovi cookie che non influenzano l'HTML pubblico
if (req.http.Cookie) {
set req.http.Cookie = regsuball(req.http.Cookie, "(^|; )_ga=[^;]*", "");
set req.http.Cookie = regsuball(req.http.Cookie, "(^|; )_gid=[^;]*", "");
set req.http.Cookie = regsuball(req.http.Cookie, "(^|; )_fbp=[^;]*", "");
set req.http.Cookie = regsuball(req.http.Cookie, "(^|; )cookie_notice_accepted=[^;]*", "");
# Se dopo la pulizia il cookie è vuoto, rimuovilo
if (req.http.Cookie ~ "^\s*$") {
unset req.http.Cookie;
}
}
```
}
sub vcl_backend_response {
# Non cacheare risposte che impostano cookie su aree dinamiche
if (bereq.url ~ "^/(cart|checkout|my-account)(/|$)") {
set beresp.uncacheable = true;
set beresp.ttl = 0s;
return (deliver);
}
```
# Per pagine pubbliche, se il backend imposta cookie inutili, valutarne la rimozione
if (bereq.method == "GET" && bereq.url !~ "^/(wp-admin|wp-login.php|cart|checkout|my-account)") {
unset beresp.http.Set-Cookie;
}
# TTL di esempio per contenuti pubblici
if (beresp.status == 200) {
set beresp.ttl = 1h;
set beresp.grace = 10m;
}
```
}
Questo non è un file da copiare ciecamente in produzione. È uno schema concettuale. In un sito reale bisogna verificare cookie effettivi, plugin installati, area riservata, multilingua, valuta, GeoIP, cache purge, header del backend e flussi di checkout.
NGINX come web cache: esempio concettuale
Quando non si usa Varnish, NGINX può comunque svolgere un ruolo di cache tramite proxy_cache o fastcgi_cache. La logica resta simile: definire una cache key, stabilire quando bypassare, decidere quando non salvare la risposta e aggiungere header diagnostici.
Esempio semplificato con reverse proxy:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=WORDPRESS:100m inactive=60m max_size=5g;
map $http_cookie $skip_cache_cookie {
default 0;
~wordpress_logged_in_ 1;
~woocommerce_items_in_cart=1 1;
~wp_woocommerce_session_ 1;
}
map $request_uri $skip_cache_uri {
default 0;
~^/wp-admin 1;
~^/wp-login.php 1;
~^/(cart|checkout|my-account) 1;
~add-to-cart= 1;
~wc-ajax= 1;
}
server {
location / {
proxy_pass http://backend_wordpress;
```
proxy_cache WORDPRESS;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_bypass $skip_cache_cookie $skip_cache_uri $http_authorization;
proxy_no_cache $skip_cache_cookie $skip_cache_uri $http_authorization;
proxy_cache_valid 200 301 302 60m;
proxy_cache_valid 404 5m;
add_header X-Cache-Status $upstream_cache_status always;
}
```
}
La distinzione tra proxy_cache_bypass e proxy_no_cache è importante. Il primo dice a NGINX di non prendere la risposta dalla cache in certe condizioni. Il secondo dice di non salvare la risposta ottenuta dal backend. In molte configurazioni vanno usati insieme, perché una richiesta privata non deve né leggere una copia condivisa né popolare la cache con una risposta privata.
Desktop e mobile: il problema dei layout diversi
Molti siti WordPress moderni usano CSS responsive e quindi servono lo stesso HTML sia a desktop sia a mobile. Questo è lo scenario migliore per la cache: una URL, una risposta HTML, un solo oggetto cacheato. Il layout cambia nel browser tramite CSS media query, non sul server.
Il problema nasce quando il backend genera HTML diverso in base allo User-Agent. Ad esempio:
<?php if ( wp_is_mobile() ) : ?> ``` <div class="mobile-menu">Menu mobile</div> ``` <?php else : ?> ``` <div class="desktop-menu">Menu desktop</div> ``` <?php endif; ?>
Se questa logica si trova in una pagina cacheabile, il primo dispositivo che genera la cache può determinare l’HTML servito agli altri. Se la cache memorizza la versione mobile, anche utenti desktop potrebbero ricevere markup mobile. Se memorizza la versione desktop, utenti mobile potrebbero ricevere markup desktop.
Ci sono tre strategie possibili.
La prima, e generalmente preferibile, è non variare l’HTML. Si genera un markup compatibile con entrambi i dispositivi e si lascia al CSS il compito di adattare l’interfaccia.
La seconda è creare una variante di cache esplicita. In Varnish si può normalizzare lo User-Agent in un header sintetico, ad esempio X-UA-Device, con valori limitati come desktop e mobile. Poi si include quell’header nella logica di variazione. L’importante è non usare direttamente l’intero User-Agent come chiave di cache, perché produrrebbe migliaia di varianti quasi inutili.
sub vcl_recv {
if (req.http.User-Agent ~ "(?i)mobile|android|iphone") {
set req.http.X-UA-Device = "mobile";
} else {
set req.http.X-UA-Device = "desktop";
}
}
sub vcl_hash {
hash_data(req.http.X-UA-Device);
}
La terza strategia è usare URL separati, come /m/ o parametri specifici, ma oggi è raramente consigliabile per siti WordPress standard, perché complica SEO, canonical, redirect, analytics e manutenzione.
In generale, lo sviluppatore dovrebbe evitare wp_is_mobile() per decidere contenuti critici all’interno di pagine cacheate. Può avere senso per micro-ottimizzazioni, ma se produce markup differente deve essere accompagnato da una strategia di cache variation chiara.
Il problema del GeoIP
Il GeoIP è un altro punto delicato. Molti siti vogliono mostrare contenuti diversi in base al paese: valuta, banner, disponibilità prodotto, messaggi legali, IVA, spedizioni, lingua o promozioni locali. Il problema è che l’indirizzo IP è un dato variabile e non sempre stabile. Inoltre, se la cache non viene istruita correttamente, si rischia di servire a un utente italiano una pagina generata per un utente tedesco, oppure di creare una variante cache per ogni IP, distruggendo l’efficienza della cache.
La regola è: mai variare la cache direttamente per IP utente, salvo casi molto specifici e controllati. Bisogna invece normalizzare il dato. Ad esempio, si può trasformare l’IP in un codice paese e usare solo quello per differenziare la cache:
IT → versione Italia DE → versione Germania FR → versione Francia OTHER → versione generica
Anche qui, però, bisogna chiedersi se è davvero necessario generare HTML diverso server-side. Per molti casi è meglio servire una pagina pubblica comune e usare JavaScript per mostrare un messaggio locale, proporre un cambio valuta o suggerire una versione linguistica. Se il contenuto localizzato non è critico per SEO o compliance, il caricamento lato browser è spesso più sicuro per la cache.
WooCommerce rende il tema ancora più sensibile. Se il prezzo cambia per paese, valuta o regime fiscale, la pagina prodotto non è più universalmente condivisibile. Le alternative sono:
- cache separata per paese o valuta normalizzata;
- prezzo caricato via endpoint dinamico;
- pagina pubblica con prezzo standard e calcolo personalizzato solo nel carrello;
- bypass per mercati o listini realmente personalizzati;
- architettura multisito o multistore quando le differenze sono strutturali.
La scelta dipende dal modello commerciale. Un blog con banner localizzato può usare JavaScript. Un eCommerce con IVA, spedizioni e prezzi diversi deve progettare la cache insieme alla logica fiscale e commerciale.
L’hashing della cache in Varnish: come creare più versioni controllate della stessa pagina
Per capire davvero come Varnish possa servire più versioni della stessa pagina senza confonderle, bisogna introdurre il concetto di hashing della cache. Quando Varnish salva una risposta in cache, non la conserva semplicemente “per URL” in modo generico. Internamente costruisce una cache key, cioè una chiave di identificazione usata per ritrovare l’oggetto cacheato nelle richieste successive.
In termini pratici, la cache key risponde a una domanda molto precisa: questa richiesta può usare lo stesso oggetto cacheato di una richiesta precedente? Se la risposta è sì, Varnish può servire un HIT. Se invece qualche elemento rilevante cambia, Varnish deve cercare un altro oggetto oppure andare al backend e generare una nuova variante.
Per impostazione predefinita, Varnish costruisce l’hash usando elementi fondamentali come req.url e req.http.host. Questo significa che, normalmente, queste due richieste finiscono su oggetti cache differenti:
https://www.example.com/prodotto/maglia/ https://www.example.com/categoria/t-shirt/
Allo stesso modo, se lo stesso Varnish serve più domini, anche l’host entra nella logica di separazione:
[www.example.com/prodotto/maglia/](http://www.example.com/prodotto/maglia/) shop.example.com/prodotto/maglia/
Anche se il path è identico, il dominio diverso deve produrre una cache diversa. Sarebbe infatti pericoloso servire contenuti generati per un host su un altro host.
Il punto interessante è che Varnish permette di estendere questa logica tramite la subroutine vcl_hash. Qui si può decidere quali elementi devono concorrere alla costruzione della chiave di cache. In altre parole, si può dire a Varnish: “questa pagina non cambia solo per URL e host, ma anche per dispositivo, lingua, paese, valuta o altro stato applicativo normalizzato”.
La cache key non deve contenere tutto: deve contenere solo ciò che cambia davvero l’HTML
Uno degli errori più comuni è pensare che, per sicurezza, convenga inserire nella cache key molti dati: cookie, User-Agent completo, IP, header vari, parametri di tracking e preferenze utente. In realtà è l’approccio opposto a quello corretto.
Una cache key efficace deve includere solo gli elementi che modificano realmente la risposta HTML. Tutto il resto genera frammentazione inutile.
Se una scheda prodotto WooCommerce è identica per tutti gli utenti anonimi, non ha senso creare una versione diversa per ogni cookie di analytics. Se il contenuto non cambia in base a _ga, _fbp, gclid o utm_source, questi elementi non devono entrare nell’hash. Altrimenti Varnish potrebbe conservare molte copie identiche della stessa pagina, riducendo drasticamente il rapporto di HIT.
Il principio da seguire è questo:
variare la cache solo quando cambia il contenuto servito, non quando cambia semplicemente il contesto della richiesta.
Come funziona vcl_hash
La subroutine vcl_hash consente di aggiungere dati alla chiave di lookup tramite hash_data(). Uno schema semplificato può essere questo:
sub vcl_hash {
hash_data(req.url);
```
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
return (lookup);
```
}
Questa è la logica di base: URL più host. Tuttavia, in un progetto WordPress o WooCommerce reale, potrebbero esserci casi in cui la stessa URL deve avere più varianti cacheate. La cosa importante è che queste varianti siano poche, prevedibili e controllate.
Ad esempio, se il sito genera HTML diverso per desktop e mobile, si potrebbe aggiungere all’hash un valore normalizzato come X-UA-Device:
sub vcl_recv {
if (req.http.User-Agent ~ "(?i)mobile|android|iphone") {
set req.http.X-UA-Device = "mobile";
} else {
set req.http.X-UA-Device = "desktop";
}
}
sub vcl_hash {
hash_data(req.url);
```
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
hash_data(req.http.X-UA-Device);
return (lookup);
```
}
In questo caso Varnish potrà conservare due versioni della stessa URL:
/prodotto/maglia/ + desktop /prodotto/maglia/ + mobile
Questo evita il problema classico in cui il primo visitatore mobile genera una pagina cacheata che poi viene servita anche agli utenti desktop, o viceversa.
Normalizzare prima di hashare
La parte davvero importante non è solo aggiungere dati all’hash, ma normalizzarli prima. Non bisogna mai inserire direttamente nell’hash valori troppo variabili, come l’intero User-Agent, l’IP completo o tutto l’header Cookie.
Uno User-Agent può contenere centinaia o migliaia di combinazioni diverse. Se viene usato direttamente come parte della cache key, la stessa pagina potrebbe avere un numero enorme di copie cacheate:
/prodotto/maglia/ + Chrome iPhone versione X /prodotto/maglia/ + Chrome iPhone versione Y /prodotto/maglia/ + Safari iPhone versione Z /prodotto/maglia/ + Chrome Android modello A /prodotto/maglia/ + Chrome Android modello B /prodotto/maglia/ + Firefox Desktop versione N
Quasi tutte queste versioni sarebbero inutili. Se l’HTML cambia solo tra mobile e desktop, la cache key deve distinguere solo tra mobile e desktop. Non tra tutti i browser del mondo.
Lo stesso vale per il GeoIP. Non bisogna usare l’IP del visitatore come parte dell’hash. Bisogna trasformarlo in una classificazione più stabile, ad esempio:
IT DE FR US OTHER
Solo dopo questa normalizzazione il valore può essere usato per creare varianti cacheate.
Esempio: cache diversa per paese GeoIP
Supponiamo che un WooCommerce mostri messaggi diversi in base al paese del visitatore. Per l’Italia mostra “Spedizione gratuita sopra i 99€”, per la Germania mostra un messaggio diverso, per tutti gli altri paesi mostra un messaggio generico.
In questo caso non ha senso creare una cache per ogni IP. Ha senso creare tre varianti:
country=IT country=DE country=OTHER
La VCL potrebbe essere concettualmente simile a questa:
sub vcl_recv {
# Esempio: l'header X-Country viene impostato da un load balancer,
# da una CDN o da una logica GeoIP precedente a Varnish.
```
if (req.http.X-Country == "IT") {
set req.http.X-Cache-Country = "IT";
} elseif (req.http.X-Country == "DE") {
set req.http.X-Cache-Country = "DE";
} else {
set req.http.X-Cache-Country = "OTHER";
}
```
}
sub vcl_hash {
hash_data(req.url);
```
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
hash_data(req.http.X-Cache-Country);
return (lookup);
```
}
Con questa configurazione, la stessa URL può avere più oggetti cacheati, ma sempre in modo controllato:
/prodotto/maglia/ + IT /prodotto/maglia/ + DE /prodotto/maglia/ + OTHER
Questo approccio è molto diverso dal bypass totale della cache. Il bypass dice: “per questo caso non uso la cache”. L’hash variation dice invece: “uso la cache, ma distinguo le versioni che sono davvero diverse”.
Esempio: cache diversa per lingua
Nel multilingua WordPress bisogna capire dove vive l’informazione della lingua. Se la lingua è nella URL, ad esempio:
/it/prodotto/maglia/ /en/product/t-shirt/ /de/produkt/t-shirt/
allora spesso non serve aggiungere altro all’hash, perché req.url è già diverso. Varnish vede URL differenti e conserva oggetti differenti.
Il problema nasce quando la lingua dipende da cookie o header, ad esempio:
/prodotto/maglia/ + cookie lingua=it /prodotto/maglia/ + cookie lingua=en
In questo scenario è necessario decidere se la lingua deve entrare nella cache key. Tuttavia, anche qui non bisogna hashare tutto il cookie. Bisogna estrarre solo il valore rilevante e trasformarlo in un header normalizzato:
sub vcl_recv {
if (req.http.Cookie ~ "site_lang=it") {
set req.http.X-Cache-Lang = "it";
} elseif (req.http.Cookie ~ "site_lang=en") {
set req.http.X-Cache-Lang = "en";
} elseif (req.http.Cookie ~ "site_lang=de") {
set req.http.X-Cache-Lang = "de";
} else {
set req.http.X-Cache-Lang = "default";
}
}
sub vcl_hash {
hash_data(req.url);
```
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
hash_data(req.http.X-Cache-Lang);
return (lookup);
```
}
In un progetto ben progettato, tuttavia, per ragioni SEO e di chiarezza architetturale, è spesso preferibile avere la lingua nella URL. Questo riduce ambiguità, semplifica canonical, sitemap, hreflang e comportamento della cache.
Esempio WooCommerce: valuta diversa
WooCommerce può diventare complesso quando prezzo e valuta cambiano in base al paese, alla lingua o a una preferenza scelta dall’utente. Se la stessa scheda prodotto mostra prezzi in euro, dollari o sterline, non è possibile servire la stessa cache HTML a tutti.
Una soluzione è creare una variante di cache per valuta:
sub vcl_recv {
if (req.http.Cookie ~ "currency=EUR") {
set req.http.X-Cache-Currency = "EUR";
} elseif (req.http.Cookie ~ "currency=USD") {
set req.http.X-Cache-Currency = "USD";
} elseif (req.http.Cookie ~ "currency=GBP") {
set req.http.X-Cache-Currency = "GBP";
} else {
set req.http.X-Cache-Currency = "EUR";
}
}
sub vcl_hash {
hash_data(req.url);
```
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
hash_data(req.http.X-Cache-Currency);
return (lookup);
```
}
Così la pagina prodotto può esistere in più versioni:
/prodotto/maglia/ + EUR /prodotto/maglia/ + USD /prodotto/maglia/ + GBP
Bisogna però fare attenzione: ogni nuova dimensione aggiunta all’hash moltiplica il numero di varianti. Se una pagina varia per dispositivo, paese e valuta, il numero totale di oggetti può crescere rapidamente.
Ad esempio:
2 dispositivi x 5 paesi x 3 valute = 30 varianti per URL
Se il catalogo ha 10.000 prodotti, il numero potenziale di oggetti cacheati diventa molto alto. Non sempre questo è un problema, ma va progettato consapevolmente. Una cache enterprise non deve semplicemente “cacheare tutto”: deve cacheare ciò che produce un vantaggio reale.
Cache variation e cookie WooCommerce
Con WooCommerce bisogna distinguere tra cookie che indicano uno stato privato e cookie che indicano una variante pubblica.
Un cookie come woocommerce_items_in_cart=1 segnala che l’utente ha prodotti nel carrello. In molti casi non conviene creare una variante pubblica della pagina prodotto per questo stato, perché il carrello è personale. La scelta più sicura è bypassare la full page cache per certe richieste oppure, meglio ancora, continuare a servire la pagina pubblica cacheata e aggiornare il mini-cart via JavaScript.
Diverso è il caso di un cookie che indica una valuta o una lingua, se quella valuta o lingua produce HTML uguale per un gruppo di utenti. In quel caso il cookie non rappresenta un utente specifico, ma una variante condivisibile. Può quindi essere estratto, normalizzato e usato nell’hash.
La distinzione è fondamentale:
- cookie di sessione personale: normalmente non deve generare una cache pubblica condivisa;
- cookie di preferenza condivisibile: può generare una variante, se il contenuto è uguale per tutti gli utenti con quella preferenza;
- cookie di tracking: non deve influenzare l’hash e dovrebbe essere ignorato dalla cache;
- cookie di autenticazione: di norma porta a bypass o a gestione applicativa molto controllata.
Quando usare l’hash variation e quando usare JavaScript
Non tutto ciò che cambia deve diventare una variante di cache. A volte è meglio usare JavaScript.
La cache variation è indicata quando il contenuto diverso è importante per la prima risposta HTML, per la SEO, per il rendering iniziale o per la coerenza commerciale. Ad esempio:
- pagina in lingua diversa;
- prezzo in valuta diversa già indicizzato o visibile nel markup;
- layout server-side realmente diverso per mobile e desktop;
- messaggi legali obbligatori per paese;
- catalogo differente per mercato.
JavaScript è invece spesso preferibile per elementi piccoli, personali o non essenziali al primo rendering:
- numero prodotti nel carrello;
- nome dell’utente loggato;
- wishlist;
- banner promozionale chiuso dall’utente;
- preferenza grafica;
- contenuti “recently viewed”;
- notifiche private.
In altre parole: se la differenza riguarda una porzione piccola e personale dell’interfaccia, meglio non moltiplicare la cache. Si serve una pagina pubblica e si completa il dettaglio lato browser. Se invece la differenza riguarda l’intero documento HTML, allora una variante di hash può essere corretta.
Un esempio completo: dispositivo, paese e lingua
In un caso più avanzato, si potrebbe voler distinguere la cache per tre dimensioni: dispositivo, paese e lingua. La VCL dovrebbe prima normalizzare tutti i valori e poi usarli dentro vcl_hash.
sub vcl_recv {
# Normalizzazione dispositivo
if (req.http.User-Agent ~ "(?i)mobile|android|iphone") {
set req.http.X-Cache-Device = "mobile";
} else {
set req.http.X-Cache-Device = "desktop";
}
```
# Normalizzazione paese
if (req.http.X-Country == "IT") {
set req.http.X-Cache-Country = "IT";
} elseif (req.http.X-Country == "DE") {
set req.http.X-Cache-Country = "DE";
} else {
set req.http.X-Cache-Country = "OTHER";
}
# Normalizzazione lingua
if (req.url ~ "^/it/") {
set req.http.X-Cache-Lang = "it";
} elseif (req.url ~ "^/en/") {
set req.http.X-Cache-Lang = "en";
} elseif (req.url ~ "^/de/") {
set req.http.X-Cache-Lang = "de";
} else {
set req.http.X-Cache-Lang = "default";
}
```
}
sub vcl_hash {
hash_data(req.url);
```
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
hash_data(req.http.X-Cache-Device);
hash_data(req.http.X-Cache-Country);
hash_data(req.http.X-Cache-Lang);
return (lookup);
```
}
Questo produce una cache key più ricca, ma ancora controllata. La stessa URL non è più una singola entità, bensì una famiglia di oggetti cacheati. Ogni oggetto rappresenta una combinazione specifica di varianti.
È importante però valutare il costo combinatorio. Se aggiungiamo troppe dimensioni, la cache si frammenta. Se ne aggiungiamo troppo poche, rischiamo di servire il contenuto sbagliato. La progettazione corretta sta nel mezzo: poche varianti, molto chiare, tutte giustificate da una reale differenza dell’HTML.
Attenzione ai purge: invalidare tutte le varianti
Quando si usano varianti di hash, bisogna considerare anche il purge. Se una scheda prodotto ha più versioni cacheate per lingua, dispositivo e paese, non basta invalidare mentalmente “la pagina prodotto”. Bisogna assicurarsi che l’invalidazione rimuova tutte le varianti corrette.
Se si fa un purge puntuale basato solo sulla URL, bisogna verificare che la logica VCL rimuova tutti gli oggetti associati a quella URL, non solo una singola variante. In alcuni casi è preferibile usare ban basati su URL, host, tag o surrogate key, così da invalidare gruppi di oggetti correlati.
Per WordPress e WooCommerce questo è particolarmente importante. Quando cambia il prezzo di un prodotto, devono sparire tutte le versioni cacheate di quella scheda prodotto: desktop, mobile, italiano, inglese, valuta euro, valuta dollaro e così via. Se viene purgata solo una variante, alcuni utenti potrebbero continuare a vedere dati obsoleti.
Hashing Varnish e responsabilità dello sviluppatore
L’hashing della cache non è solo un tema sistemistico. Riguarda direttamente anche lo sviluppo applicativo. Lo sviluppatore deve sapere quali condizioni modificano davvero l’output HTML e deve comunicarle in modo prevedibile al livello cache.
Nel caso WordPress e WooCommerce, questo significa evitare logiche nascoste e difficili da controllare. Se un plugin cambia prezzo in base a un cookie, quel cookie deve essere noto. Se un tema cambia layout in base a wp_is_mobile(), la cache deve avere una variante mobile/desktop. Se un sistema B2B mostra listini diversi per ruolo utente, bisogna decidere se bypassare, variare per gruppo o caricare i prezzi dinamicamente.
Una buona architettura dovrebbe documentare chiaramente:
- quali pagine sono pubbliche e condivisibili;
- quali pagine sono private e sempre in pass;
- quali cookie sono ignorabili;
- quali cookie indicano sessione o login;
- quali header entrano nella cache key;
- quali varianti esistono per ogni URL;
- come vengono purgate tutte le varianti.
Questo documento dovrebbe essere condiviso tra sviluppatore, sistemista e chi gestisce l’eCommerce. Senza questa visione comune, si rischia di avere Varnish configurato correttamente dal punto di vista tecnico, ma sabotato da comportamenti applicativi imprevedibili.
La sintesi: cache diverse sì, ma solo quando servono
L’hashing di Varnish permette di creare più versioni cacheate della stessa URL e quindi di gestire casi complessi come mobile/desktop, lingua, valuta, paese o altri stati applicativi condivisibili. È uno strumento molto potente perché evita il falso dilemma tra “cache unica per tutti” e “bypass totale”.
La scelta corretta non è eliminare la cache appena compare una differenza, ma capire se quella differenza può essere trasformata in una variante controllata. Quando è possibile, Varnish può servire oggetti diversi con la stessa efficienza di una normale cache, purché la cache key sia progettata bene.
La regola finale è semplice: non bisogna hashare tutto ciò che arriva dal client, ma solo ciò che cambia realmente il contenuto della risposta. URL, host, dispositivo normalizzato, paese normalizzato, lingua o valuta possono essere ottimi candidati. Cookie interi, IP completi, User-Agent grezzi e parametri di tracking sono invece quasi sempre segnali troppo rumorosi.
Per WordPress e WooCommerce questo approccio consente di mantenere alte prestazioni anche in scenari complessi: cataloghi multilingua, negozi multi-valuta, contenuti localizzati, layout differenziati e campagne marketing. Ma richiede metodo. La cache non deve inseguire casualmente lo stato applicativo: deve ricevere dallo sviluppo segnali chiari, normalizzati e stabili.
Utenti loggati: quando bypassare e quando separare
Gli utenti loggati sono una delle ragioni principali per bypassare la cache. WordPress imposta cookie di autenticazione e un cookie wordpress_logged_in_* che indica lo stato di login. Se una richiesta contiene quel cookie, la cache deve essere molto prudente, perché il backend potrebbe generare contenuti personalizzati.
Per un sito editoriale, spesso la scelta più semplice è: utenti anonimi serviti da cache, utenti loggati in pass. Questo va bene quando il numero di utenti loggati è basso rispetto al traffico anonimo.
Per siti membership, LMS, community o B2B, invece, bypassare tutti gli utenti loggati può annullare gran parte del beneficio. In questi casi bisogna progettare un livello più raffinato:
- pagine pubbliche cacheate anche per utenti loggati, se identiche;
- frammenti personali caricati via JavaScript;
- endpoint privati non cacheati per notifiche e dati account;
- cache per ruolo o gruppo, se il contenuto è uguale per tutti gli utenti dello stesso gruppo;
- header
Cache-Control: privatesulle pagine davvero personali.
Il punto non è “utente loggato uguale cache impossibile”. Il punto è capire quali parti della pagina dipendono davvero dall’identità dell’utente. Se cambia solo la scritta “Ciao Marco”, non ha senso rendere non cacheabile un’intera pagina da 200 KB. Si cachea la pagina e si aggiorna la barra utente con JavaScript.
JavaScript come alleato della cache
JavaScript è spesso visto come un problema di performance, ma in presenza di cache HTTP può diventare un alleato formidabile. La ragione è semplice: consente di servire una pagina HTML pubblica e cacheabile, lasciando al browser il compito di completare piccole parti dinamiche.
Esempi di elementi che si prestano bene a essere gestiti con JavaScript:
- stato login nel menu;
- numero prodotti nel carrello;
- wishlist;
- messaggi di consenso cookie;
- banner già chiusi dall’utente;
- recently viewed products;
- notifiche leggere;
- preferenze di interfaccia;
- contenuti GeoIP non critici;
- tracking e campagne.
Prendiamo il mini-cart WooCommerce. Se il numero di prodotti nel carrello viene stampato server-side dentro l’header, l’intera pagina potrebbe dover variare per sessione. Meglio stampare un placeholder cacheabile:
<a href="/cart/" class="cart-link"> Carrello <span id="cart-count" data-default="0">0</span> </a>
Poi aggiornare il numero via endpoint:
fetch('/wp-json/ms/v1/cart-count', {
credentials: 'same-origin'
})
.then(response => response.ok ? response.json() : { count: 0 })
.then(data => {
const counter = document.getElementById('cart-count');
if (counter) {
counter.textContent = data.count || 0;
}
});
Questa architettura ha un vantaggio importante: la pagina principale può restare identica per tutti, mentre il dato privato viaggia in una richiesta separata. Quella richiesta può essere esclusa dalla cache, protetta, limitata, ottimizzata e monitorata senza compromettere il caching globale del sito.
AJAX, REST API e admin-ajax.php
In WordPress esistono vari modi per fornire dati dinamici: admin-ajax.php, REST API, endpoint custom, template dedicati o chiamate WooCommerce wc-ajax. Dal punto di vista della cache, è importante che questi endpoint siano chiaramente separati dalle pagine pubbliche.
admin-ajax.php è molto usato, ma può diventare un collo di bottiglia perché carica WordPress e spesso viene chiamato frequentemente. La REST API può essere più ordinata, soprattutto per endpoint custom ben progettati. In entrambi i casi bisogna evitare chiamate inutili, polling aggressivo e risposte non cacheabili quando potrebbero invece avere una breve cache privata o pubblica.
Esempio di endpoint REST per una barra utente:
add_action('rest_api_init', function () {
register_rest_route('ms/v1', '/user-bar', [
'methods' => 'GET',
'callback' => function () {
if (!is_user_logged_in()) {
return [
'logged_in' => false,
];
}
```
$user = wp_get_current_user();
return [
'logged_in' => true,
'name' => $user->display_name,
'account' => wc_get_page_permalink('myaccount'),
];
},
'permission_callback' => '__return_true',
]);
```
});
Per un endpoint di questo tipo bisogna assicurarsi che la cache non lo memorizzi come risposta pubblica uguale per tutti. Si possono usare header specifici:
add_filter('rest_post_dispatch', function ($result, $server, $request) {
if ($request->get_route() === '/ms/v1/user-bar') {
nocache_headers();
}
```
return $result;
```
}, 10, 3);
La separazione è la chiave: HTML pubblico in full page cache, micro-dati personali fuori dalla full page cache.
Nonce, form e pagine cacheate
WordPress usa spesso nonce per proteggere azioni e form. Il problema è che un nonce stampato dentro una pagina cacheata può scadere o essere condiviso in modo improprio. Questo può causare errori apparentemente casuali: form che non si inviano, azioni AJAX che falliscono, checkout che si comporta male o messaggi “session expired”.
La soluzione dipende dal tipo di form. Un form pubblico semplice, come una newsletter gestita da un provider esterno, può non avere bisogno di un nonce WordPress server-side. Un form che modifica dati, invia ordini o compie azioni sensibili deve invece stare in una pagina non cacheata oppure deve recuperare il token dinamicamente.
Per WooCommerce il checkout deve rimanere fuori dalla cache proprio perché contiene stato, nonce, sessione, metodi di spedizione e dati cliente. Non è una pagina da ottimizzare con full page cache. È una pagina da ottimizzare con buone query, object cache, PHP performante, database sano e riduzione del codice superfluo.
Query string, UTM e frammentazione della cache
Un altro problema applicativo sottovalutato riguarda le query string. URL come:
/prodotto/example/?utm_source=facebook /prodotto/example/?utm_source=google /prodotto/example/?fbclid=... /prodotto/example/?gclid=...
possono creare molte copie cache della stessa pagina, anche se il contenuto HTML è identico. Questo fenomeno si chiama frammentazione della cache: invece di avere un unico oggetto riutilizzato, si producono decine o migliaia di varianti inutili.
La soluzione è normalizzare o rimuovere dalla cache key i parametri che non influenzano il rendering. In Varnish si può ripulire la URL o usare VMOD specifici; in NGINX si può definire una cache key che ignora determinati parametri, oppure riscrivere le richieste prima del lookup.
Attenzione però: non tutti i parametri sono inutili. In WooCommerce, parametri come filtro categoria, ordinamento, paginazione o ricerca possono cambiare realmente il contenuto. Quelli devono restare nella cache key. Parametri di tracking come utm_source, utm_medium, fbclid, gclid normalmente non dovrebbero generare varianti HTML.
Header diagnostici: rendere visibile il comportamento della cache
Uno sviluppatore non può lavorare bene con una cache invisibile. Bisogna poter capire se una risposta è HIT, MISS, BYPASS, PASS o EXPIRED. Per questo è utile aggiungere header diagnostici.
Con Varnish si può aggiungere, ad esempio:
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
```
set resp.http.X-Cache-Hits = obj.hits;
```
}
Con NGINX:
add_header X-Cache-Status $upstream_cache_status always;
Questi header permettono allo sviluppatore di verificare subito se una modifica al tema, a un plugin o agli header HTTP ha rotto la cache. Durante il debug bisogna controllare anche:
Cache-Control;Set-Cookie;Cookienella richiesta;Vary;- status code;
- metodo HTTP;
- query string;
- header di autenticazione;
- eventuali redirect.
Una pagina pubblica che restituisce sempre X-Cache: MISS o BYPASS va analizzata. Spesso la causa è un cookie, un header no-cache, un plugin che apre sessione o una variazione non normalizzata.
Vary: potente ma da usare con cautela
L’header Vary indica alla cache che la risposta cambia in base a uno o più header della richiesta. È utile, ma può essere pericoloso se usato male.
Un esempio comune e sensato è:
Vary: Accept-Encoding
In questo caso la cache distingue tra risposte compresse e non compresse. Più delicato è:
Vary: User-Agent
Questo può generare un numero enorme di varianti perché gli User-Agent sono moltissimi. Meglio normalizzare prima lo User-Agent in poche classi, come desktop e mobile, e variare su un header sintetico controllato.
Ancora più rischioso:
Vary: Cookie
Variare su tutto l’header Cookie significa spesso rendere la cache quasi inutile, perché ogni utente può avere una combinazione diversa di cookie. La strategia migliore è evitare Vary: Cookie generico e scegliere con precisione quali stati devono influenzare la cache.
Cache purge e invalidazione dei contenuti
Una cache utile deve anche essere invalidabile. In WordPress, quando un articolo viene aggiornato, quando un prodotto cambia prezzo o quando una categoria viene modificata, non si può aspettare sempre la scadenza naturale del TTL. Serve una strategia di purge.
In un sito WordPress semplice, quando si aggiorna un post, si dovrebbe purgare almeno:
- la URL del post;
- la homepage, se mostra gli ultimi articoli;
- le categorie associate;
- gli archivi tag;
- feed o pagine correlate, se cacheati.
In WooCommerce, quando cambia un prodotto, possono essere coinvolti:
- pagina prodotto;
- categoria prodotto;
- homepage se mostra prodotti in evidenza;
- blocchi “prodotti correlati”;
- ricerca interna;
- feed prodotto;
- landing page con shortcode WooCommerce.
Il purge deve essere chirurgico ma non ingenuo. Purgare troppo poco lascia contenuti obsoleti. Purgare tutto a ogni modifica crea tempeste di MISS e carico sul backend. Una buona integrazione WordPress/Varnish dovrebbe conoscere le relazioni tra contenuti e invalidare gli oggetti giusti.
Object cache e full page cache non sono la stessa cosa
Molti sviluppatori confondono object cache e full page cache. Sono strumenti diversi e complementari.
La full page cache conserva la risposta HTML finale e può evitare completamente PHP. Varnish e NGINX reverse cache lavorano a questo livello.
La object cache, ad esempio con Redis o Memcached, conserva risultati intermedi: query, oggetti WordPress, transients, opzioni e dati applicativi. PHP viene comunque eseguito, ma trova dati già pronti più velocemente.
In WooCommerce, una buona object cache è importante per carrello, checkout, area account e utenti loggati, cioè per tutte le richieste che non possono beneficiare della full page cache. La full page cache accelera il traffico pubblico; l’object cache aiuta il traffico dinamico.
Una piattaforma performante usa entrambi i livelli, senza confonderli.
Checklist per sviluppatori WordPress in presenza di Varnish
Quando si sviluppa o si modifica un sito WordPress/WooCommerce dietro Varnish o altra Web Cache, conviene seguire una checklist pratica.
-
- La pagina contiene dati diversi per ogni utente?
- Il tema usa
is_user_logged_in()per stampare HTML personalizzato? - Il plugin imposta cookie su tutte le pagine?
- Viene avviata una sessione PHP globale?
- Il carrello WooCommerce è stampato server-side nell’header?
- Il prezzo cambia per paese, valuta, ruolo o gruppo?
- La versione mobile genera HTML diverso?
- La lingua è nella URL o dipende da cookie/header?
- Le query string di tracking frammentano la cache?
- Le pagine cart, checkout e my-account sono escluse?
- Gli endpoint AJAX/REST personali sono esclusi dalla cache pubblica?
- Gli header
Cache-Controlsono coerenti? - La risposta contiene
Set-Cookiesenza motivo? - Esiste un sistema di purge dopo aggiornamenti contenuto?
- Gli header diagnostici permettono di vedere HIT/MISS/BYPASS?
Queste domande dovrebbero entrare nel normale processo di sviluppo, come già accade per sicurezza, SEO, accessibilità e compatibilità mobile.
Conclusione: sviluppare pensando alla cache
Una Web Cache non è un ostacolo allo sviluppo. È un moltiplicatore di performance, ma richiede disciplina. Il codice applicativo deve distinguere chiaramente tra contenuto pubblico e contenuto privato, tra HTML condivisibile e dati personali, tra cookie necessari e cookie superflui, tra varianti reali e frammentazione accidentale.
Varnish Cache, in particolare, offre un controllo estremamente potente, ma non può correggere da solo un’applicazione che genera stato privato ovunque. Se WordPress imposta cookie inutili su tutte le pagine, se WooCommerce viene personalizzato stampando carrello e dati utente server-side in ogni template, se il GeoIP varia per IP non normalizzato o se il layout mobile viene generato senza una cache key coerente, la cache perde efficacia.
La strada corretta è progettare l’applicazione con una separazione netta:
- HTML pubblico e condivisibile servito da Varnish o Web Cache;
- frammenti personali caricati via JavaScript, REST API o endpoint dedicati;
- pagine realmente private escluse dalla full page cache;
- cookie controllati e non usati come deposito generico;
- varianti normalizzate per mobile, lingua, valuta o paese solo quando necessarie;
- purge coerente quando il contenuto cambia.
Per uno sviluppatore WordPress e WooCommerce, lavorare bene con Varnish significa smettere di pensare alla pagina come a qualcosa che viene rigenerato sempre, per tutti, a ogni richiesta. Significa invece progettare risposte HTTP cacheabili, prevedibili e sicure. Il risultato è un sito più veloce, un backend più scarico, una maggiore resistenza ai picchi di traffico e una migliore esperienza per l’utente finale.
In un’infrastruttura gestita professionalmente, la cache non è un semplice plugin da attivare. È una parte centrale dell’architettura. E quando sviluppo applicativo, configurazione sistemistica e logiche di business lavorano nella stessa direzione, Varnish può esprimere tutto il suo valore: servire rapidamente ciò che è pubblico, proteggere ciò che è privato e mantenere WordPress e WooCommerce performanti anche sotto carico reale.