Single Page Application e Core Web Vitals: navigazioni soft, idratazione e il problema delle metriche

Nelle SPA l’LCP misura solo il primo caricamento e CLS e INP si accumulano su tutta la sessione: come si comportano le metriche, il costo dell’idratazione, le risposte dei framework moderni e le alternative.

Una singola pagina al centro collegata da linee tratteggiate a quattro indirizzi diversi, con la funzione history.pushState

Negli ultimi quindici anni una parte crescente del web è stata costruita come Single Page Application, o SPA: applicazioni che caricano una sola pagina HTML e poi gestiscono tutta la navigazione con JavaScript, cambiando contenuti e indirizzo senza mai ricaricare la pagina. React, Vue, Angular, Svelte e i framework costruiti sopra di loro, come Next.js e Nuxt, sono diventati gli strumenti standard per molti progetti, compresi siti che un tempo sarebbero stati realizzati con un CMS tradizionale. Anche nel mondo WordPress l’approccio «headless», con WordPress come sorgente dei contenuti e un front-end in JavaScript, è sempre più diffuso.

Le Core Web Vitals, però, sono nate pensando al modello tradizionale, in cui ogni clic su un link carica una pagina nuova. Applicate a una SPA producono effetti inattesi: metriche che misurano solo la prima pagina, valori che si accumulano su tutta la sessione, differenze notevoli tra ciò che l’utente percepisce e ciò che i dati riportano. In questo articolo vediamo come le metriche si comportano nelle SPA, quali sono i problemi di prestazioni tipici di questo modello, come i framework moderni li affrontano e come misurare davvero l’esperienza.

In un sito tradizionale, ogni clic su un link produce una navigazione completa: il browser chiede una nuova pagina al server, scarta quella precedente e costruisce la nuova da zero. Ogni navigazione ha il suo LCP, il suo CLS e il suo INP, misurati e attribuiti all’indirizzo di quella pagina. È il modello per cui le Core Web Vitals sono state progettate, e quello su cui si basa il Chrome UX Report.

In una SPA, solo la prima visita è una navigazione completa, detta «hard». I clic successivi sono navigazioni «soft»: il JavaScript intercetta il clic, scarica i dati necessari, aggiorna una parte della pagina e cambia l’indirizzo con l’API History, tramite history.pushState(). Per l’utente l’esperienza è quella di una nuova pagina; per il browser, dal punto di vista delle metriche, è sempre la stessa pagina che cambia contenuto.

Confronto tra sito tradizionale e SPA: nel sito tradizionale ogni pagina, dalla home alla categoria, al prodotto, al carrello, è un caricamento completo con LCP, CLS e INP misurati per quella pagina; nella SPA solo la home ha un caricamento completo con il suo LCP, le altre pagine sono navigazioni soft con pushState, e CLS e INP vengono accumulati su tutta la sessione e attribuiti alla prima URL
Nelle SPA le navigazioni successive alla prima non producono nuove misure di caricamento.

Le conseguenze sulle metriche sono tre. L’LCP viene misurato solo sulla prima pagina: le navigazioni soft, anche se lente, non producono un nuovo LCP nei dati di campo. Il CLS e l’INP, invece, vengono osservati per tutta la vita della pagina, cioè per tutta la sessione dell’utente nella SPA, e attribuiti all’indirizzo con cui la sessione è iniziata. Un utente che entra dalla home, naviga per dieci minuti tra categorie e prodotti e incontra un’interazione lenta nel carrello contribuisce con quell’INP lento ai dati della home.

Il risultato è un quadro distorto in due direzioni. Da un lato, l’LCP delle SPA tende a sembrare migliore di quanto sia l’esperienza complessiva, perché le navigazioni successive, che l’utente percepisce come caricamenti, non vengono misurate. Dall’altro, CLS e INP delle pagine d’ingresso tendono a sembrare peggiori, perché raccolgono problemi che avvengono altrove e su sessioni molto lunghe.

La misura delle navigazioni soft

Il problema è noto, e il team di Chrome lavora da tempo a una soluzione: un meccanismo che riconosce le navigazioni soft, quando un’interazione dell’utente cambia l’indirizzo e aggiorna in modo significativo il contenuto, e le tratta come navigazioni a sé, con il proprio LCP, CLS e INP. Il meccanismo è stato sperimentato in diverse fasi e la libreria web-vitals ne offre un supporto sperimentale. Al momento della scrittura, però, non fa parte delle Core Web Vitals ufficiali, non è usato nel Chrome UX Report e non influisce sulla valutazione di Google.

In attesa che diventi standard, chi gestisce una SPA ha due strade complementari: misurare le navigazioni soft per conto proprio, con un sistema di Real User Monitoring che registri il tempo tra il clic e il completamento dell’aggiornamento, e interpretare i dati ufficiali sapendo cosa includono e cosa no.

// Misura semplificata di una navigazione soft
router.beforeEach( () => {
	performance.mark( 'navigazione-inizio' );
} );

router.afterEach( async () => {
	await new Promise( ( r ) => requestAnimationFrame( () => setTimeout( r, 0 ) ) );
	performance.mark( 'navigazione-fine' );
	const m = performance.measure( 'navigazione-soft', 'navigazione-inizio', 'navigazione-fine' );
	invia( { nome: 'soft-nav', valore: m.duration, pagina: location.pathname } );
} );

Il primo caricamento: rendering nel client

Il problema di prestazioni più classico delle SPA riguarda proprio la prima pagina. Nella forma più semplice, il rendering nel client, il server invia un HTML quasi vuoto, con un contenitore e un riferimento a un grande file JavaScript. Il browser deve scaricare quel file, analizzarlo ed eseguirlo; il codice deve richiedere i dati a un’API, attendere la risposta e solo allora costruire la pagina. Fino a quel momento l’utente vede una pagina bianca o uno scheletro di caricamento.

Su un computer veloce con una buona connessione la catena può sembrare rapida; su uno smartphone di fascia media, con centinaia di kilobyte di JavaScript da eseguire, l’LCP può facilmente superare i tre o quattro secondi. Il browser, inoltre, non può scoprire in anticipo l’immagine principale, perché non esiste nell’HTML: viene inserita dal JavaScript alla fine della catena, e tutte le ottimizzazioni viste nell’articolo sull’LCP, dal preload scanner alla priorità alta, non possono agire.

Rendering sul server e idratazione

La risposta dei framework moderni è il rendering sul server: il server esegue lo stesso codice dell’applicazione, produce un HTML completo e lo invia al browser, che può mostrarlo subito. Il contenuto e l’immagine principale sono nel documento, e l’LCP torna a essere paragonabile a quello di un sito tradizionale. Quando le pagine possono essere generate in anticipo, al momento della pubblicazione, si parla di generazione statica, con risultati ancora migliori.

Ma l’HTML generato sul server è solo un’immagine della pagina: per renderla interattiva, il browser deve comunque scaricare ed eseguire il JavaScript dell’applicazione, che ricostruisce in memoria lo stato dei componenti e collega i gestori degli eventi agli elementi esistenti. Questo processo si chiama idratazione, e su pagine complesse può richiedere centinaia di millisecondi di lavoro sul main thread.

Tre modi di costruire la stessa pagina: con il rendering nel client l’HTML vuoto è seguito da JavaScript e dati API, e LCP e interattività arrivano insieme molto tardi; con il rendering sul server e idratazione l’HTML completo produce un LCP rapido, ma la pagina diventa interattiva solo dopo una lunga idratazione; con isole o componenti server l’LCP è rapido e l’interattività arriva poco dopo grazie a un JavaScript ridotto
Il rendering sul server anticipa l’LCP; ridurre il JavaScript da idratare accorcia la «valle» in cui la pagina è visibile ma non risponde.

Tra il momento in cui la pagina è visibile e quello in cui è interattiva si apre una sorta di valle: l’utente vede pulsanti e menu, li tocca, ma non succede nulla, oppure la risposta arriva con molto ritardo perché il main thread è occupato dall’idratazione. È uno scenario che peggiora direttamente l’INP, e che è particolarmente frustrante perché la pagina sembra pronta.

Le risposte dei framework moderni

Negli ultimi anni i framework hanno sviluppato diverse strategie per ridurre il costo dell’idratazione, con un principio comune: inviare al browser meno JavaScript.

  • Architettura a isole. Framework come Astro generano pagine interamente statiche e rendono interattive solo le parti che ne hanno bisogno, le «isole», come un carrello o un modulo di ricerca. Il resto della pagina non viene idratato e non richiede JavaScript.
  • Componenti server. I React Server Components, usati per esempio da Next.js, permettono di eseguire alcuni componenti solo sul server: il loro codice non viene mai inviato al browser, e solo i componenti interattivi vengono idratati.
  • Idratazione progressiva e selettiva. Diversi framework idratano prima i componenti visibili o quelli con cui l’utente interagisce, e rimandano il resto.
  • Ripresa invece di idratazione. Approcci come quello di Qwik evitano del tutto l’idratazione: lo stato viene serializzato nell’HTML e il codice di un componente viene scaricato solo quando serve, al primo clic.
  • Streaming. Il server invia l’HTML a pezzi, man mano che i dati sono pronti, così il browser può mostrare subito le parti veloci della pagina senza attendere le più lente.

Nessuna di queste strategie è automatica: un’applicazione Next.js può essere leggerissima o pesantissima a seconda di come viene scritta. Ma il framework giusto, usato con consapevolezza, permette di ottenere prestazioni paragonabili a quelle di un buon sito tradizionale anche con un’architettura moderna.

I problemi delle navigazioni soft

Il vantaggio promesso dalle SPA è la navigazione fluida: niente ricaricamenti, transizioni rapide, stato conservato. È un vantaggio reale quando l’applicazione è ben costruita, ma diverse pratiche comuni lo annullano.

  • Cascate di richieste. Il componente della nuova pagina viene caricato, poi richiede i dati, poi i componenti figli richiedono altri dati. Ogni passaggio aspetta il precedente, e la navigazione soft può durare più di un caricamento completo. La soluzione è caricare codice e dati in parallelo, già al passaggio del mouse o al clic.
  • Spostamenti del layout. La nuova pagina compare a pezzi, prima la struttura, poi le immagini senza dimensioni, poi i dati: ogni arrivo sposta il contenuto, e questi spostamenti si sommano al CLS della sessione.
  • Nessun riscontro al clic. Se il clic avvia il caricamento ma la pagina non cambia finché tutto non è pronto, l’utente non capisce se il clic ha funzionato. Un riscontro immediato, anche solo un indicatore di caricamento, migliora la percezione e l’INP.
  • Memoria e sessioni lunghe. Componenti che non vengono rimossi correttamente, ascoltatori di eventi che si accumulano, dati in memoria mai liberati: nelle sessioni lunghe l’applicazione diventa via via più lenta, e ogni interazione più pesante.
  • Posizione di scorrimento e focus. Dopo una navigazione soft, la pagina deve tornare in cima, e il focus deve spostarsi in modo coerente per chi usa la tastiera o un lettore di schermo. Quando questo non avviene, l’esperienza si degrada anche se le metriche sembrano buone.

Il caso headless WordPress

Un’architettura sempre più diffusa usa WordPress solo come sistema di gestione dei contenuti, esposti tramite REST API o GraphQL, e un front-end separato costruito con un framework JavaScript. I vantaggi dichiarati sono flessibilità, sicurezza e prestazioni. Le prestazioni, però, dipendono interamente da come viene costruito il front-end: un sito headless con generazione statica e poco JavaScript può essere velocissimo; uno con rendering nel client e un grande pacchetto applicativo può essere molto più lento del sito WordPress tradizionale che ha sostituito.

Prima di scegliere questa strada per ragioni di prestazioni, vale la pena confrontarla con ciò che un sito WordPress tradizionale ben ottimizzato può già offrire: cache di pagina, un tema leggero, pochi script, e le tecniche moderne che rendono la navigazione tra pagine quasi istantanea anche senza una SPA.

L’alternativa: pagine tradizionali con strumenti moderni

Uno dei motivi originali per scegliere una SPA era la fluidità delle transizioni tra pagine. Oggi il browser offre strumenti che rendono questa fluidità possibile anche con pagine tradizionali, senza i costi di un’applicazione JavaScript. Le Speculation Rules permettono di caricare in anticipo la pagina successiva e di aprirla istantaneamente; la back/forward cache rende immediato il ritorno alle pagine visitate; le View Transitions tra documenti, supportate nei browser recenti, permettono animazioni fluide tra una pagina e l’altra con poche righe di CSS.

/* Transizioni animate tra pagine tradizionali, senza JavaScript */
@view-transition {
	navigation: auto;
}

.copertina-articolo {
	view-transition-name: copertina;
}

Per molti siti di contenuti, cataloghi e siti aziendali, la combinazione di pagine tradizionali generate sul server, cache efficiente, prerender e transizioni animate offre un’esperienza paragonabile a quella di una SPA, con metriche misurate correttamente per ogni pagina e molto meno JavaScript. Le SPA restano la scelta naturale per vere applicazioni, come pannelli di controllo, editor e strumenti con molto stato condiviso tra le schermate.

Come misurare una SPA

Per valutare correttamente una SPA servono più livelli di misura. I dati del Chrome UX Report restano il riferimento per il primo caricamento e per come Google vede il sito, ma vanno letti sapendo che CLS e INP includono tutta la sessione. Un sistema di RUM dovrebbe registrare separatamente le metriche della navigazione iniziale e la durata delle navigazioni soft, con l’indirizzo di destinazione, e per l’INP l’elemento e la pagina in cui l’interazione è avvenuta, non solo l’indirizzo di ingresso.

Nei test di laboratorio, oltre al caricamento iniziale, conviene registrare con il pannello Performance dei DevTools alcune navigazioni tipiche, con il rallentamento della CPU attivo: il clic su un prodotto dalla categoria, l’aggiunta al carrello, il passaggio al checkout. Sono i momenti in cui l’utente giudica la fluidità dell’applicazione, e che i test tradizionali, concentrati sul primo caricamento, non vedono.

In sintesi

Le Single Page Application cambiano il modo in cui le Core Web Vitals descrivono l’esperienza: l’LCP misura solo il primo caricamento, mentre CLS e INP si accumulano su tutta la sessione e vengono attribuiti alla pagina d’ingresso. Chrome sta lavorando a una misura dedicata alle navigazioni soft, ma per ora chi gestisce una SPA deve integrare i dati ufficiali con misure proprie.

Sul piano delle prestazioni, il rendering nel client penalizza il primo caricamento, il rendering sul server lo risolve ma introduce il costo dell’idratazione, e i framework moderni lo riducono inviando meno JavaScript con isole, componenti server e idratazione selettiva. Per molti siti di contenuti, però, la strada più semplice verso un’esperienza fluida passa oggi da pagine tradizionali arricchite da prerender, back/forward cache e View Transitions: la fluidità di una SPA, con la leggerezza e la misurabilità di un sito classico.

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 ↗