TL;DR: un chatbot AI sul sito non è una funzione, è codice di terze parti che gira sul thread principale del browser di ogni visitatore. Si paga in INP, in LCP e in interazioni lente proprio dove l’utente sta decidendo se contattarti. Qui c’è il protocollo per misurare quanto costa davvero sul tuo WordPress, la soglia oltre la quale va tolto dalle pagine di conversione e il pattern a facciata che lo fa pagare solo a chi lo apre.
La richiesta arriva sempre nello stesso modo: «mettiamo l’assistente AI sul sito, così risponde lui alle domande». Si installa un plugin, si incolla uno snippet, il bottone compare in basso a destra e il progetto è chiuso. Tre mesi dopo il cliente scrive che il sito «è diventato lento sul telefono» e nessuno collega le due cose, perché fra le due cose non c’è nessuna misura.
Questo articolo è quella misura: cosa guardare, con quali strumenti, quale numero decide, e cosa fare quando il numero è brutto.
Il chatbot AI non è una feature: è uno script di terze parti
Il punto di partenza è amministrativo prima che tecnico. Quando incolli il loader di un widget, chi decide cosa gira su ogni pagina del sito non è più chi ha costruito il sito: è il fornitore del widget, che pubblica una nuova versione del bundle quando vuole e senza avvisarti. La tua build è versionata, la loro no — per te.
Cosa entra in pagina con un assistente AI, tipicamente: un loader, un bundle applicativo, una connessione verso un dominio terzo, spesso font e icone proprie, a volte una connessione persistente aperta per la conversazione. Fin qui è la stessa fattura di un live chat classico.
La differenza la fa lo streaming. Un chatbot AI non risponde con una richiesta e una risposta: riceve la risposta a pezzi e aggiorna l’interfaccia mentre arriva. Ogni pezzo è lavoro sul thread principale, ripetuto per tutta la durata della conversazione. Il widget di chat tradizionale costava all’apertura; questo costa mentre è in uso, che è anche il momento in cui l’utente è più attivo sulla pagina.
Cosa misura l’INP, e perché il widget lo colpisce
INP (Interaction to Next Paint) osserva la latenza delle interazioni dell’utente e restituisce un valore sotto il quale sta la quasi totalità delle interazioni della pagina. Le interazioni osservate sono tre: clic con il mouse, tap su touchscreen, pressione di un tasto. Scroll e hover non contano. Le interazioni dentro gli iframe invece sì: incapsulare il widget in un iframe non è un’assicurazione contro l’INP.
Le soglie, misurate al 75° percentile dei caricamenti reali e separate fra mobile e desktop: fino a 200 millisecondi la responsività è buona, fra 200 e 500 va migliorata, sopra 500 è scarsa.
Il meccanismo che conta per il nostro caso è l’input delay. Il clic dell’utente su «Richiedi un preventivo» non viene servito subito: aspetta che il thread principale finisca l’attività in corso. Un’attività che occupa il thread per più di 50 millisecondi è una long task, e una long task del widget davanti al clic sul tuo CTA è esattamente il caso peggiore: il costo lo paga l’interazione che ti porta il lavoro, non quella del chatbot.
C’è un dato di Chrome che spiega perché questa metrica è la più severa con i widget: circa il 90% del tempo che una persona passa su una pagina è dopo il caricamento. L’INP guarda proprio quella finestra, cioè quella in cui il chatbot vive.
Dove sta il debito oggi: i numeri del campo
Prima di ottimizzare conviene sapere dove sta il problema nel web reale. Il dataset Chrome UX Report di giugno 2026, pubblicato il 14 luglio 2026 su 18.210.974 origini, dice questo: il 55,3% delle origini supera i Core Web Vitals, l’LCP è buono nel 67,7% dei casi, il CLS nell’81,4%, l’INP nell’85,9%.
Va letto in due direzioni. L’INP è la metrica che più siti passano: significa che partire con un INP buono è la normalità, e che un widget pesante può spostarti da «passa» a «non passa» praticamente da solo. E l’LCP resta il collo di bottiglia principale del web: uno script di terze parti caricato in fase di load compete per banda e connessioni proprio mentre il browser dovrebbe dipingere il contenuto principale, quindi il chatbot non paga solo sulla responsività.
Google usa i Core Web Vitals fra i segnali di page experience. Non è il primo fattore di ranking e chi lo racconta così vende qualcosa, ma è uno dei pochi su cui puoi intervenire in un pomeriggio con un effetto verificabile.
Il protocollo di misura: tre passaggi, nessuna opinione
1. Dati di campo, sulle URL che contano. Interroga PageSpeed Insights o la CrUX API sull’URL specifica delle pagine più importanti, non sull’origine: la media del sito nasconde la pagina che converte. Tieni presente come è fatto quel numero — la CrUX API restituisce una media mobile sui 28 giorni precedenti, aggiornata una volta al giorno. Dopo una modifica non ha senso guardare i dati il giorno dopo e trarre conclusioni: la finestra si svuota lentamente.
2. A/B in laboratorio, nello stesso browser. Apri DevTools, pannello Network, blocca il dominio del widget con «Block request domain». Passa al pannello Performance, imposta CPU throttling 4x e rete Slow 4G, registra e interagisci con il CTA reale della pagina, non con un punto vuoto. Ripeti tre volte, poi rifai tutto con il widget attivo, poi ancora con il widget attivo e la conversazione aperta: quel terzo scenario è quello che nessuno misura e quello in cui il costo è massimo. I numeri da confrontare sono il Total Blocking Time, le long task attribuite al dominio del widget, il numero di richieste e i byte.
Il device conta più dello strumento. Se misuri sul tuo Mac stai misurando il sito di un altro: il visitatore tipo di una PMI arriva da un Android di fascia media di tre anni fa. Il throttling 4x è un’approssimazione di quel telefono, non un handicap arbitrario.
3. Attribuzione in produzione. Installa la libreria web-vitals nella build con attribuzione: per l’interazione peggiore restituisce l’elemento coinvolto e la ripartizione fra input delay, processing e presentation delay. Manda quei campi a GA4 come dimensioni personalizzate. Senza attribuzione hai un numero e nessun colpevole, e un numero senza colpevole non produce nessuna decisione.
Un limite da conoscere prima di fidarti del risultato: la libreria gira nel documento principale e non vede dentro un iframe cross-origin. Se il widget è servito così — molti prodotti commerciali lo sono — CrUX ti farà comunque pagare quelle interazioni, ma la tua attribuzione tornerà vuota. In quel caso il verdetto arriva dal passo 2 in laboratorio, bloccando il dominio del widget, non dal campo.
Il conto: quando il chatbot va tolto
La domanda giusta non è «quanto pesa». È «quanti contatti porta in più rispetto a un form fatto bene e a un numero di telefono cliccabile». Un widget si giustifica con il suo rendimento, non con la sua esistenza.
Un esempio aritmetico, non una misura del tuo sito: il widget genera 6 conversazioni al mese, una diventa cliente, e nello stesso periodo l’INP di campo su mobile passa da 180 a 320 millisecondi su tutte le pagine. Hai comprato un lead al mese pagandolo con la responsività di ogni sessione del sito, incluse quelle delle persone arrivate dalla SERP per leggere un articolo e mai interessate a chattare. Su tremila sessioni mensili quel prezzo è alto e va deciso da chi paga, non dedotto dal plugin installato.
Il criterio operativo che applico è a tre condizioni, e devono valere insieme:
- le conversazioni sono tracciate come sorgente di conversione con un evento, non stimate a occhio;
- il costo misurato in INP è sotto i 50 millisecondi di delta al 75° percentile mobile;
- il widget non compare nelle pagine dove la conversione è un’altra: preventivo, contatti, checkout, landing di campagna.
Se salta una delle tre, la prima azione non è «ottimizziamo»: è togliere il widget da quelle pagine. È reversibile, costa dieci minuti e libera la discussione dal terreno delle opinioni.
Il pattern a facciata: farlo pagare solo a chi lo apre
Lighthouse ha un audit dedicato a questo problema, «lazy load third-party resources with facades». L’idea è semplice: al posto del widget metti un elemento tuo, statico e leggero, che ne imita l’aspetto. Lo script vero si carica al primo clic.
Il risultato è una redistribuzione del costo che ha senso: chi non apre la chat non scarica nulla e non subisce nessuna long task; chi la apre paga il caricamento una volta sola, in un momento in cui sta già aspettando una risposta e quindi tollera l’attesa. Il prezzo è un clic in più, e chi vuole davvero scrivere lo fa comunque.
Su un child theme WordPress la facciata sta in una funzione sola:
// functions.php del child theme: facciata al posto del loader del widget.
add_action( 'wp_footer', 'lr_chat_facade' );
function lr_chat_facade() {
// Niente chatbot dove la conversione e' un'altra cosa.
// is_checkout() esiste solo con WooCommerce attivo: va protetta.
$spento = is_page( array( 'preventivo', 'contatti' ) )
|| ( function_exists( 'is_checkout' ) && is_checkout() )
|| is_page_template( 'template-landing.php' );
if ( $spento ) {
return;
}
?>
<button type="button" id="lr-chat-open" class="lr-chat-facade">
<?php esc_html_e( 'Assistenza', 'lucarimediotti' ); ?>
</button>
<script>
document.getElementById( 'lr-chat-open' ).addEventListener( 'click', function () {
var btn = this;
if ( btn.dataset.loaded ) {
return;
}
btn.dataset.loaded = '1';
// aria-busy: chi usa uno screen reader sa che sta caricando qualcosa.
btn.setAttribute( 'aria-busy', 'true' );
var s = document.createElement( 'script' );
s.src = 'https://widget.esempio.com/loader.js';
s.async = true;
s.addEventListener( 'load', function () {
// Qui va la chiamata di apertura del fornitore, altrimenti il
// visitatore deve cliccare due volte. Esempi: Intercom( 'show' ),
// $crisp.push( [ 'do', 'chat:open' ] ).
// Il launcher vero compare dopo il bootstrap del fornitore, non al
// load dello script: si aspetta lui, si sposta il focus, POI si
// nasconde la facciata. Ordine invertito = focus perso sul body.
var tentativi = 0;
var attesa = setInterval( function () {
var launcher = document.querySelector( '.widget-launcher' ); // selettore del fornitore
tentativi++;
if ( launcher ) {
clearInterval( attesa );
launcher.focus();
btn.removeAttribute( 'aria-busy' );
btn.hidden = true;
} else if ( tentativi > 40 ) {
// 10 secondi: il widget non si e' disegnato, la facciata resta
// cliccabile invece di restare visibile e inerte.
clearInterval( attesa );
btn.removeAttribute( 'aria-busy' );
delete btn.dataset.loaded;
}
}, 250 );
} );
// E' uno script di terze parti: il caricamento puo' fallire. Il bottone
// torna cliccabile invece di restare annunciato "occupato" per sempre.
s.addEventListener( 'error', function () {
btn.removeAttribute( 'aria-busy' );
delete btn.dataset.loaded;
} );
document.head.appendChild( s );
} );
</script>
<?php
}
Tre dettagli fanno la differenza fra una facciata e una pezza. Il primo è l’accessibilità: la facciata è un <button> reale, raggiungibile da tastiera, non un <div> con un gestore di clic. Niente aria-expanded, però: la facciata non apre un pannello che controlla lei, apre quello del fornitore, e annunciare «espanso» su un contenitore vuoto è peggio che non annunciare niente. Il secondo è la staffetta, ed è la parte che quasi tutti sbagliano: l’evento load dello script dice che il loader è stato eseguito, non che il fornitore ha disegnato il suo launcher — quel pezzo arriva dopo, con una richiesta di configurazione e spesso l’iniezione di un iframe. Se nascondi la facciata al load, nasconderai l’elemento che in quel momento ha il focus e lo rimanderai al body: chi naviga da tastiera ha chiesto una chat e si ritrova a metà pagina, senza niente da premere. Per questo lo snippet aspetta la comparsa del launcher, gli passa il focus e solo dopo si toglie di mezzo, con un tetto di dieci secondi oltre il quale la facciata resta dov’è. Vale anche il caso opposto: lo script di terze parti può non arrivare affatto, e l’handler error serve a restituire un bottone cliccabile invece di uno bloccato su aria-busy. Il selettore .widget-launcher e la chiamata di apertura vanno sostituiti con quelli del tuo fornitore: li trovi nella sua documentazione o con l’ispettore. Il terzo è il controllo delle pagine: la condizione in cima al file è il vero guadagno, perché toglie il costo dove non serve invece di limitarsi a spostarlo. is_checkout() arriva da WooCommerce, quindi va sempre protetta con function_exists(), e per le landing di campagna il criterio più stabile è il template, non lo slug.
Se stai valutando l’AI come integrazione lato server invece che come widget in pagina, la logica è la stessa che ho usato per le automazioni AI read-only su WordPress: mettere in produzione la parte che non può rompere niente e misurarla, prima di dare al fornitore un pezzo di thread principale.
Le tre scorciatoie che non funzionano
«L’ho messo in async, quindi non blocca.» async e defer spostano il momento del download e dell’esecuzione, non il fatto che l’esecuzione avvenga sul thread principale. Le long task restano identiche, solo più tardi — cioè spostate dalla finestra dell’LCP a quella dell’INP, dove l’utente sta già cliccando. È utile, ma non è una soluzione.
«Lo carico dopo tre secondi con un timeout.» Peggio: fai arrivare il costo mentre la persona sta leggendo e magari sta cliccando qualcosa. L’input delay colpisce un’interazione reale al posto di nessuna. Il ritardo fisso è la scelta comoda per chi non vuole toccare il markup; la facciata è quella corretta, perché lega il costo a un’intenzione.
«Lo metto in un web worker.» Spostare uno script di terze parti in un worker con un proxy del DOM funziona su tag analitici e male su widget che disegnano interfaccia e leggono il layout. Un chatbot in streaming aggiorna il DOM continuamente: la sincronizzazione fra worker e thread principale diventa lei stessa il costo. Prima di provarci, misura — a volte il risultato è peggiore dell’originale, e lo scopri solo con il pannello Performance aperto.
La quarta opzione, quella che funziona e che nessuno propone volentieri, è ridurre la superficie: chatbot su blog e FAQ, dove l’utente ha una domanda vera, e niente chatbot sulle pagine dove l’azione è già chiara.
La take: trattalo come una campagna, non come un plugin
Un plugin lo installi e te ne dimentichi. Una campagna ha un budget, un costo per contatto e una data di revisione. Il chatbot AI ha il profilo economico della seconda, non della prima: costo continuo — token da una parte, performance dall’altra — rendimento in principio misurabile, e una decisione onesta da prendere quando il rendimento non c’è.
L’errore più frequente che vedo non è tecnico: è che nessuno possiede quel numero. Il fornitore del widget misura le conversazioni, l’azienda misura i lead totali, l’agenzia misura le posizioni, e l’INP non lo misura nessuno. Senza un proprietario, il widget resta in pagina per sempre, e ogni anno pesa un po’ di più perché il bundle cresce.
La versione forte, che è anche quella che mi costa qualche discussione: se l’assistente AI serve a rispondere a domande già scritte nelle FAQ, non ti serve un widget. Ti serve una pagina fatta bene. È l’unica delle due che un motore di risposta può leggere e citare — e come ho scritto parlando di dove si sposta il valore di un sito, la citabilità del contenuto oggi vale più dell’interfaccia che lo incarta. Il widget parla a una persona alla volta, dopo che l’ha trovato; la pagina risponde anche a chi non arriverà mai.
Cosa fare questa settimana
- Elenca ogni script di terze parti attivo sulle cinque pagine più visitate, con il dominio e il nome di chi l’ha voluto. La colonna «chi l’ha voluto» è quella che sbloccherà la decisione.
- Rileva INP e LCP di campo su quelle cinque URL specifiche, non sull’origine, e annota la data del rilevamento accanto al numero.
- Fai l’A/B in DevTools bloccando il dominio del widget, con CPU 4x e Slow 4G, tre ripetizioni, includendo lo scenario con la conversazione aperta.
- Se il delta INP al 75° percentile mobile supera i 50 millisecondi, metti la facciata e togli il widget dalle pagine di conversione. Nell’ordine: prima togli, poi ottimizza quello che resta.
- Aggiungi
web-vitalscon attribuzione, manda le dimensioni a GA4 e fissa una data di revisione a 30 giorni. Se una decisione non ha scadenza, non si prende.
Il costo delle richieste ripetute è un tema parallelo e vale la stessa disciplina: ne ho scritto a proposito della cache delle chiamate AI su WordPress, dove il conto si vede in fattura invece che in millisecondi. Stessa logica, unità di misura diversa.
Fonti
- web.dev — Interaction to Next Paint (INP): soglie, tipi di interazione, iframe
- Chrome UX Report — Release notes, dataset giugno 2026 pubblicato il 14 luglio 2026
- Chrome UX Report API — media mobile su 28 giorni e aggiornamento giornaliero
- web.dev — Optimize long tasks
- Lighthouse — Lazy load third-party resources with facades
- web.dev — Best practices per gli script di terze parti
- Google Search Central — Page experience e Core Web Vitals
- GoogleChrome/web-vitals — libreria di misura con build di attribuzione

