LCP Optimization: Image Preloading, Critical CSS e SSR
Il Largest Contentful Paint (LCP) misura il tempo necessario perche il contenuto principale della pagina sia visibile all'utente. Per Google, la soglia "Good" e 2.5 secondi: oltre questa soglia, il ranking organico viene penalizzato. Secondo i dati CrUX di marzo 2026, il 35% dei siti web ha ancora LCP nel range "Needs Improvement" o "Poor", spesso per ragioni evitabili con interventi mirati.
L'elemento LCP e tipicamente l'immagine hero, il banner principale, o il blocco di testo piu
grande above the fold. La strategia di ottimizzazione dipende dal tipo di elemento: un'immagine
richiede approcci diversi rispetto a un testo. In entrambi i casi, ci sono quattro tecniche ad
alto impatto che coprono il 90% dei casi reali: preloading dell'immagine LCP, inlining del
Critical CSS, preloading dei font con font-display: swap, e Server-Side Rendering.
Cosa Imparerai
- Come identificare l'elemento LCP con Chrome DevTools e Lighthouse
- Image preloading con <link rel="preload"> e l'attributo fetchpriority="high"
- Critical CSS: cosa e, come estrarlo e come includerlo inline nell'HTML
- Font preloading con font-display: swap e preconnect
- Come SSR (Server-Side Rendering) migliora strutturalmente il LCP
- Come misurare il miglioramento LCP prima e dopo le ottimizzazioni
Identificare l'Elemento LCP
Il primo passo e sempre sapere quale elemento e il tuo LCP. Puoi identificarlo in due modi:
- Chrome DevTools: nel pannello Performance, dopo una registrazione del caricamento pagina, cerca il marcatore "LCP" nella timeline. Cliccaci sopra per vedere quale elemento ha triggerato l'LCP e quante risorse dipendenti ha richiesto.
-
web-vitals.js: la property
entriesdell'oggetto metric LCP contiene laLargestContentfulPaintentry con l'elemento DOM corrispondente.
import { onLCP } from 'web-vitals';
onLCP((metric) => {
const lcpEntry = metric.entries[metric.entries.length - 1];
console.log('Elemento LCP:', lcpEntry.element);
console.log('Valore LCP:', metric.value, 'ms');
console.log('Rating:', metric.rating); // 'good' | 'needs-improvement' | 'poor'
});
Strategia 1: Image Preloading con fetchpriority
Se il tuo elemento LCP e un'immagine, il miglioramento piu impattante e il preloading
esplicito con alta priorita. Per default, il browser scopre le immagini solo quando
il parser HTML incontra il tag <img>, che puo avvenire molto dopo che il
documento ha iniziato a caricarsi. Con <link rel="preload">, il browser
inizia a scaricare l'immagine il prima possibile.
<!-- Nel <head> dell'HTML, prima di altri tag -->
<link
rel="preload"
as="image"
href="/images/hero.webp"
fetchpriority="high"
imagesrcset="/images/hero-400.webp 400w,
/images/hero-800.webp 800w,
/images/hero-1200.webp 1200w"
imagesizes="100vw"
/>
L'attributo fetchpriority="high" (Baseline 2023, 92% supporto) comunica al
browser che questa risorsa e critica e deve essere scaricata prima delle risorse con priorita
normale. Su reti lente, questo puo fare la differenza di 0.5-1.5 secondi sull'LCP.
Immagini Responsive: imagesrcset e imagesizes
Se usi immagini responsive con srcset, devi aggiungere imagesrcset
e imagesizes al link di preload per fare in modo che il browser precarichi
la variante corretta per il viewport corrente, non la versione piu grande.
Formato WebP e AVIF
L'ottimizzazione del formato immagine e complementare al preloading. WebP riduce tipicamente il peso delle immagini del 25-35% rispetto a JPEG. AVIF riduce del 50-55% ma ha tempi di decodifica piu alti su hardware meno recente. Per l'immagine LCP, WebP e il formato ottimale nel 2026, con AVIF per un progressivo miglioramento su browser moderni.
<picture>
<source
srcset="/images/hero.avif"
type="image/avif"
/>
<source
srcset="/images/hero-400.webp 400w, /images/hero-800.webp 800w"
type="image/webp"
sizes="100vw"
/>
<img
src="/images/hero.jpg"
alt="Hero image"
width="1200"
height="600"
fetchpriority="high"
loading="eager"
/>
</picture>
Non usare loading="lazy" sull'elemento LCP
loading="lazy" ritarda il download dell'immagine finche non e vicina al viewport.
Per l'elemento LCP, che e per definizione gia nel viewport, questo causa un ritardo inutile.
Usa loading="eager" (o nessun attributo loading) per l'immagine LCP.
Applica loading="lazy" solo alle immagini below the fold.
Strategia 2: Critical CSS Inline
Per default, il browser non inizia a renderizzare la pagina finche non ha scaricato e parsato
tutti i CSS linkati nell'<head>. Se il tuo file CSS e grande (100KB+) e
serve su una CDN lenta, questo puo aggiungere 0.5-2 secondi all'LCP. La soluzione e il
Critical CSS: estrarre solo i CSS necessari per renderizzare il contenuto
above the fold e includerli inline nell'HTML.
<!-- INVECE DI: -->
<link rel="stylesheet" href="/styles/main.css">
<!-- USA: -->
<style>
/* Critical CSS inline - solo quello necessario above the fold */
body { margin: 0; font-family: sans-serif; }
.hero { width: 100%; height: 60vh; background: #f5f5f5; }
.hero h1 { font-size: 2.5rem; color: #333; }
nav { display: flex; justify-content: space-between; padding: 1rem; }
</style>
<!-- Carica il CSS completo in modo non-bloccante -->
<link
rel="preload"
as="style"
href="/styles/main.css"
onload="this.rel='stylesheet'"
/>
<noscript><link rel="stylesheet" href="/styles/main.css"></noscript>
Strumenti per Estrarre il Critical CSS
Estrarre manualmente il Critical CSS non e praticabile su siti grandi. Usa strumenti automatici:
- Critters (plugin webpack/Vite): estrae e inline il CSS critico automaticamente
- critical (npm): tool CLI per estrarre il critical CSS da qualsiasi URL
- PurgeCSS + Critters: combinazione ideale per ridurre il CSS totale prima dell'inline
// vite.config.ts - con Critters per Critical CSS automatico
import { defineConfig } from 'vite';
import critters from 'vite-plugin-critters';
export default defineConfig({
plugins: [
critters({
// Inline il critical CSS, carica il resto in modo asincrono
strategy: 'critical',
// Non rimuovere i CSS non usati (lascialo a PurgeCSS)
pruneSource: false,
}),
],
});
Strategia 3: Font Preloading con font-display: swap
I web font possono bloccare il rendering del testo (FOIT - Flash of Invisible Text) fino a 3 secondi su connessioni lente, ritardando l'LCP se il testo e l'elemento LCP. Due ottimizzazioni si combinano per eliminare questo problema:
1. font-display: swap dice al browser di mostrare il testo immediatamente con il font di fallback, poi sostituirlo con il font web quando e disponibile. Elimina il FOIT.
/* In @font-face o nel parametro di Google Fonts */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: swap; /* Critico per LCP */
font-weight: 400 700;
}
/* Con Google Fonts: aggiungi &display=swap all'URL */
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap"
rel="stylesheet"
/>
2. Preconnect + Preload riduce la latenza di scoperta e download del font:
<!-- Preconnect: stabilisce la connessione con il font server in anticipo -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Preload: scarica il file font prima che il CSS venga parsato -->
<link
rel="preload"
as="font"
type="font/woff2"
href="/fonts/inter-400.woff2"
crossorigin
/>
Strategia 4: Server-Side Rendering
Le quattro strategie precedenti ottimizzano le risorse; SSR ottimizza la struttura fondamentale della pagina. In un'applicazione SPA (Single Page Application), il browser riceve HTML quasi vuoto e deve scaricare, parsare ed eseguire JavaScript per renderizzare il contenuto. Con SSR, il browser riceve HTML gia renderizzato: l'elemento LCP e immediatamente visibile senza aspettare il JavaScript.
Il miglioramento LCP con SSR e tipicamente di 0.5-2 secondi:
- SPA senza SSR: HTML shell → JS download (300-800ms) → JS execution (100-500ms) → API calls (200-800ms) → render → LCP
- Con SSR: HTML completo → LCP immediato (solo il tempo di download dell'HTML e il TTFB del server)
Framework come Next.js, Angular Universal (SSR), Nuxt.js e SvelteKit includono SSR nativo.
Per Angular, la configurazione e gestita da angular.json e dal server Express:
// angular.json - abilita SSR
{
"projects": {
"app": {
"architect": {
"build": {
"configurations": {
"production": {
"server": "src/main.server.ts",
"prerender": false, // SSR on-demand
"ssr": true
}
}
}
}
}
}
}
Misurare il Miglioramento LCP
Dopo aver implementato le ottimizzazioni, misura il miglioramento con questi strumenti:
- Lighthouse CI: crea baseline prima delle ottimizzazioni e confronta dopo. Esegui piu run (5+) e usa la mediana per ridurre la varianza.
- WebPageTest.org: ottieni filmstrip e waterfall dettagliato del caricamento su dispositivi reali e connessioni reali.
- web-vitals.js in produzione: monitora il P75 dell'LCP reale degli utenti per 7-14 giorni dopo il deploy.
import { onLCP } from 'web-vitals';
// Monitora LCP in produzione
onLCP({ reportAllChanges: true }, (metric) => {
// Invia solo il valore finale (quando la pagina e in background)
if (metric.entries.length > 0) {
navigator.sendBeacon('/api/vitals', JSON.stringify({
metric: 'LCP',
value: metric.value,
rating: metric.rating,
url: window.location.pathname,
device: navigator.connection?.effectiveType || 'unknown',
}));
}
});
Impatto Combinato delle Quattro Strategie
Applicare tutte e quattro le strategie in combinazione su una SPA tipica con immagine hero, web fonts e CSS bundled produce miglioramenti reali:
- Image preloading + fetchpriority: -300-600ms LCP
- Critical CSS inline: -200-500ms LCP (elimina render-blocking)
- Font preloading + font-display:swap: -100-300ms LCP
- SSR: -500-2000ms LCP (dipende dalla complessita della SPA)
Il risultato totale puo portare un LCP da 4-6 secondi a 1.5-2.5 secondi, passando da "Poor" a "Good" in modo consistente.
Conclusioni
L'ottimizzazione del LCP e un'area dove pochi interventi mirati producono impatti enormi. Il 35% dei siti con LCP nel range "Needs Improvement" o "Poor" ha quasi certamente uno o piu di questi quattro problemi: immagine hero non precaricata, CSS che blocca il rendering, font che ritardano il testo, o SPA senza SSR.
Il workflow corretto e: 1) misura il LCP reale con web-vitals.js, 2) identifica l'elemento LCP, 3) analizza il waterfall del caricamento per trovare il collo di bottiglia, 4) applica le ottimizzazioni rilevanti, 5) verifica il miglioramento con Lighthouse CI e dati di campo.







