Un componente immagine racchiude le best practice per il rendimento e fornisce una soluzione pronta all'uso per ottimizzare le immagini.
Le immagini sono una fonte comune di colli di bottiglia nelle prestazioni delle applicazioni web e un'area di interesse chiave per l'ottimizzazione. Le immagini non ottimizzate contribuiscono al bloat della pagina e rappresentano oltre il 70% del peso totale della pagina in byte al 90°th percentile. I diversi modi per ottimizzare le immagini richiedono un "componente immagine" intelligente con soluzioni di rendimento integrate per impostazione predefinita.
Il team di Aurora ha utilizzato Next.js per creare uno di questi componenti. L'obiettivo era creare un modello di immagine ottimizzato che gli sviluppatori web potessero personalizzare ulteriormente. Il componente funge da buon modello e stabilisce uno standard per la creazione di componenti immagine in altri framework, sistemi di gestione dei contenuti (CMS) e tech-stack. Abbiamo collaborato alla realizzazione di un componente simile per Nuxt.js e stiamo lavorando con Angular all'ottimizzazione delle immagini nelle versioni future. Questo post illustra come abbiamo progettato il componente Next.js Image e le lezioni che abbiamo imparato strada facendo.
Opportunità e problemi di ottimizzazione delle immagini
Le immagini influiscono non solo sul rendimento, ma anche sull'attività. Il numero di immagini su una pagina è stato il secondo miglior predittore delle conversioni degli utenti che visitano i siti web. Le sessioni in cui gli utenti hanno effettuato una conversione avevano il 38% in meno di immagini rispetto alle sessioni in cui non hanno effettuato una conversione. Nell'ambito del suo controllo delle best practice, Lighthouse elenca diverse opportunità per ottimizzare le immagini e migliorare gli indicatori chiave web. Di seguito sono riportate alcune delle aree comuni in cui le immagini possono influire sui Core Web Vitals e sull'esperienza utente.
Le immagini senza dimensioni influiscono negativamente sul CLS
Le immagini pubblicate senza specificare le dimensioni possono causare instabilità del layout e contribuire a un Cumulative Layout Shift (CLS) elevato. L'impostazione degli attributi width
e height
negli elementi img può aiutare a evitare variazioni del layout. Ad esempio:
<img src="flower.jpg" width="360" height="240">
La larghezza e l'altezza devono essere impostate in modo che le proporzioni dell'immagine visualizzata siano vicine alle proporzioni naturali. Una differenza significativa nelle proporzioni può causare una distorsione dell'immagine. Una proprietà relativamente nuova che ti consente di specificare proporzioni in CSS può aiutarti a ridimensionare le immagini in modo adattabile, evitando al contempo il CLS.
Le immagini di grandi dimensioni possono influire negativamente sul valore LCP
Maggiore è la dimensione del file di un'immagine, maggiore sarà il tempo necessario per il download. Un'immagine di grandi dimensioni potrebbe essere l'immagine "hero" della pagina o l'elemento più significativo nell'area visibile responsabile dell'attivazione della metrica Largest Contentful Paint (LCP). Un'immagine che fa parte dei contenuti critici e richiede molto tempo per il download ritarderà il LCP.
In molti casi, gli sviluppatori possono ridurre le dimensioni delle immagini attraverso una migliore compressione e l'utilizzo di immagini reattive. Gli attributi srcset
e sizes
dell'elemento <img>
consentono di fornire file immagine con dimensioni diverse. Il browser può quindi scegliere quello giusto in base alle dimensioni e alla risoluzione dello schermo.
Una compressione delle immagini scadente può influire negativamente sul tempo di caricamento della prima pagina
I formati di immagini moderni come AVIF o WebP possono offrire una compressione migliore rispetto ai formati JPEG e PNG di uso comune. Una compressione migliore riduce le dimensioni del file dal 25% al 50% in alcuni casi, mantenendo la stessa qualità dell'immagine. Questa riduzione comporta download più rapidi con un minor consumo di dati. L'app deve pubblicare formati di immagini moderni per i browser che supportano questi formati.
Il caricamento di immagini non necessarie influisce negativamente sul valore LCP
Le immagini sotto la piega o non nell'area visibile della pagina non vengono mostrate all'utente quando la pagina viene caricata. Possono essere differiti in modo da non contribuire all'LCP e ritardarlo. Puoi utilizzare il caricamento lento per caricare queste immagini in un secondo momento mentre l'utente scorre verso di loro.
Sfide di ottimizzazione
I team possono valutare il costo del rendimento dovuto ai problemi elencati in precedenza e implementare soluzioni di best practice per superarli. Tuttavia, nella pratica questo non accade spesso e le immagini inefficienti continuano a rallentare il web. Di seguito sono elencati alcuni possibili motivi:
- Priorità: in genere gli sviluppatori web tendono a concentrarsi su codice, JavaScript e ottimizzazione dei dati. Di conseguenza, potrebbero non essere a conoscenza di problemi relativi alle immagini o di come ottimizzarle. Le immagini create dai designer o caricate dagli utenti potrebbero non essere in alto nell'elenco delle priorità.
- Soluzione pronta all'uso: anche se gli sviluppatori sono a conoscenza delle sfumature dell'ottimizzazione delle immagini, l'assenza di una soluzione all-in-one pronta all'uso per il loro framework o tech-stack potrebbe essere un deterrente.
- Immagini dinamiche: oltre alle immagini statiche che fanno parte dell'applicazione, le immagini dinamiche vengono caricate dagli utenti o provengono da database esterni o CMS. Può essere difficile definire le dimensioni di queste immagini in cui l'origine è dinamica.
- Sovraccarico di markup: le soluzioni per includere le dimensioni delle immagini o
srcset
per dimensioni diverse richiedono un markup aggiuntivo per ogni immagine, il che può essere noioso. L'attributosrcset
è stato introdotto nel 2014, ma oggi è utilizzato solo dal 26,5% dei siti web. Quando utilizzanosrcset
, gli sviluppatori devono creare immagini di varie dimensioni. Strumenti come just-gimme-an-img possono essere utili, ma devono essere utilizzati manualmente per ogni immagine. - Supporto dei browser: i formati di immagini moderni come AVIF e WebP creano file di immagini più piccoli, ma richiedono una gestione speciale sui browser che non li supportano. Gli sviluppatori devono utilizzare strategie come la negoziazione dei contenuti o l'elemento
<picture
> in modo che le immagini vengano pubblicate su tutti i browser. - Complicazioni del caricamento lento: sono disponibili più tecniche e librerie per implementare il caricamento lento per le immagini below the fold. Scegliere la migliore può essere una sfida. Gli sviluppatori potrebbero anche non conoscere la distanza migliore dal "ripiegamento" per caricare le immagini differite. Le diverse dimensioni dell'area visibile sui dispositivi possono complicare ulteriormente il problema.
- Scenario in evoluzione: man mano che i browser iniziano a supportare nuove funzionalità HTML o CSS per migliorare il rendimento, può essere difficile per gli sviluppatori valutarle tutte. Ad esempio, Chrome sta introducendo la funzionalità Priorità di recupero come prova dell'origine. Può essere utilizzato per aumentare la priorità di immagini specifiche nella pagina. In generale, gli sviluppatori troverebbero più semplice se questi miglioramenti venissero valutati e implementati a livello di componente.
Componente immagine come soluzione
Le opportunità disponibili per ottimizzare le immagini e le difficoltà di implementarle singolarmente per ogni applicazione ci hanno portato all'idea di un componente immagine. Un componente immagine può incapsulare e applicare le best practice. Sostituendo l'elemento <img>
con un componente immagine, gli sviluppatori possono risolvere meglio i problemi di rendimento delle immagini.
Nell'ultimo anno, abbiamo collaborato con il framework Next.js per progettare e implementare il componente immagine. Può essere utilizzato come sostituto diretto degli elementi <img>
esistenti nelle app Next.js come segue.
// Before with <img> element:
function Logo() {
return <img src="/logo.jpg" alt="logo" height="200" width="100" />
}
// After with image component:
import Image from 'next/image'
function Logo() {
return <Image src="/logo.jpg" alt="logo" height="200" width="100" />
}
Il componente cerca di risolvere problemi relativi alle immagini in modo generico attraverso un'ampia gamma di funzionalità e principi. Include inoltre opzioni che consentono agli sviluppatori di personalizzarla per vari requisiti delle immagini.
Protezione dalle variazioni di layout
Come discusso in precedenza, le immagini senza dimensioni causano variazioni di layout e contribuiscono al CLS. Quando utilizzano il componente Image di Next.js, gli sviluppatori devono fornire le dimensioni di un'immagine utilizzando gli attributi width
e height
per evitare eventuali cambiamenti di layout. Se la dimensione è sconosciuta, gli sviluppatori devono specificare layout=fill
per pubblicare un'immagine senza dimensioni all'interno di un contenitore di dimensioni. In alternativa, puoi utilizzare le importazioni di immagini statiche per recuperare le dimensioni dell'immagine effettiva sul disco rigido al momento della creazione e includerla nell'immagine.
// Image component with width and height specified
<Image src="/logo.jpg" alt="logo" height="200" width="100" />
// Image component with layout specified
<Image src="/hero.jpg" layout="fill" objectFit="cover" alt="hero" />
// Image component with image import
import Image from 'next/image'
import logo from './logo.png'
function Logo() {
return <Image src={logo} alt="logo" />
}
Poiché gli sviluppatori non possono utilizzare il componente Immagine senza specificarne le dimensioni, il design garantisce che prenderanno il tempo di considerare le dimensioni delle immagini e di evitare variazioni del layout.
Facilitare la reattività
Per rendere le immagini adattabili su tutti i dispositivi, gli sviluppatori devono impostare gli attributi srcset
e sizes
nell'elemento <img>
. Volevamo ridurre questo impegno con il componente Immagine. Il componente Next.js Image è progettato in modo da impostare i valori degli attributi una sola volta per applicazione. Li applichiamo a tutte le istanze del componente Immagine in base alla modalità di layout. Abbiamo trovato una soluzione in tre parti:
- Proprietà
deviceSizes
: questa proprietà può essere utilizzata per configurare una tantum i breakpoint in base ai dispositivi comuni alla base utenti dell'applicazione. I valori predefiniti per gli intervalli sono inclusi nel file di configurazione. - Proprietà
imageSizes
: si tratta anche di una proprietà configurabile utilizzata per ottenere le dimensioni delle immagini corrispondenti alle interruzioni delle dimensioni del dispositivo. - Attributo
layout
in ogni immagine: viene utilizzato per indicare come utilizzare le proprietàdeviceSizes
eimageSizes
per ogni immagine. I valori supportati per la modalità di layout sonofixed
,fill
,intrinsic
eresponsive
Quando viene richiesta un'immagine con le modalità di layout adattabile o compila, Next.js identifica l'immagine da pubblicare in base alle dimensioni del dispositivo che richiede la pagina e imposta srcset
e sizes
nell'immagine in modo appropriato.
Il seguente confronto mostra come la modalità di layout può essere utilizzata per controllare le dimensioni dell'immagine su schermi diversi. Abbiamo utilizzato un'immagine demo condivisa nella documentazione di Next.js, visualizzata su uno smartphone e un laptop standard.
Schermo del laptop | Schermo del telefono |
---|---|
Layout = intrinseco: viene ridotto per adattarsi alla larghezza del contenitore nelle aree visibili più piccole. Non deve essere ridimensionata oltre le dimensioni intrinseche dell'immagine in un'area visibile più grande. La larghezza del container è al 100% | |
Layout = Fisso: l'immagine non è adattabile. La larghezza e l'altezza sono fisse, come per l'elemento "", indipendentemente dal dispositivo su cui viene visualizzato. | |
Layout = Adattabile: riduci o aumenta la scala in base alla larghezza del contenitore in aree visibili diverse, mantenendo le proporzioni. | |
Layout = Riempi: larghezza e altezza vengono estese per riempire il contenitore principale. (In questo esempio, la larghezza del <div> principale è impostata su 300 x 500)
|
|
Fornire il caricamento lento integrato
Il componente Immagine fornisce come impostazione predefinita una soluzione di caricamento lento integrata e dalle prestazioni elevate. Quando utilizzi l'elemento <img>
, sono disponibili alcune opzioni per il caricamento differito, ma tutte presentano svantaggi che le rendono difficili da utilizzare. Uno sviluppatore potrebbe adottare uno dei seguenti approcci di caricamento differito:
- Specifica l'attributo
loading
: è supportato su tutti i browser moderni. - Utilizza l'API Intersection Observer: la creazione di una soluzione personalizzata di caricamento lento richiede uno sforzo e una progettazione e un'implementazione ponderate. Gli sviluppatori potrebbero non avere sempre il tempo per farlo.
- Importa una libreria di terze parti per il caricamento lento delle immagini: potrebbe essere necessario un ulteriore impegno per valutare e integrare una libreria di terze parti adatta per il caricamento lento.
Nel componente Image di Next.js, il caricamento è impostato su "lazy"
per impostazione predefinita. Il caricamento lento viene implementato utilizzando Intersection Observer, che è disponibile sulla maggior parte dei browser moderni. Gli sviluppatori non devono fare nulla di più per attivarla, ma possono disattivarla in caso di necessità.
Precarica le immagini importanti
Molto spesso, gli elementi LCP sono immagini, mentre quelle di grandi dimensioni possono ritardare la risoluzione LCP. È consigliabile precaricare immagini fondamentali in modo che il browser possa individuarle prima. Quando utilizzi un elemento <img>
, un suggerimento di precaricamento può essere incluso nell'intestazione HTML come segue.
<link rel="preload" as="image" href="important.png">
Un componente immagine ben progettato dovrebbe offrire un modo per modificare la sequenza di caricamento delle immagini, indipendentemente dal framework utilizzato. Nel caso del componente Next.js Image, gli sviluppatori possono indicare un'immagine che può essere precaricata correttamente utilizzando l'attributo priority
del componente immagini.
<Image src="/hero.jpg" alt="hero" height="400" width="200" priority />
L'aggiunta di un attributo priority
semplifica il markup ed è più comoda da usare. Gli sviluppatori di componenti di immagini possono anche esplorare le opzioni per applicare l'euristica per automatizzare il precaricamento delle immagini in primo piano nella pagina che soddisfano criteri specifici.
Promuovi l'hosting di immagini ad alto rendimento
Le CDN di immagini sono consigliate per automatizzare l'ottimizzazione delle immagini e supportano anche formati delle immagini moderni come WebP e AVIF. Il componente Next.js Image utilizza per impostazione predefinita una CDN di immagini con un'architettura di caricamento. L'esempio seguente mostra che il caricatore consente la configurazione della CDN nel file di configurazione di Next.js.
module.exports = {
images: {
loader: 'imgix',
path: 'https://ImgApp/imgix.net',
},
}
Con questa configurazione, gli sviluppatori possono utilizzare URL relativi nell'origine dell'immagine e il framework concatena l'URL relativo con il percorso CDN per generare l'URL assoluto. Sono supportate le CDN di immagini più diffuse, come Imgix, Cloudinary e Akamai. L'architettura supporta l'utilizzo di un cloud provider personalizzato implementando una funzione loader
personalizzata per l'app.
Supporto delle immagini self-hosted
In alcuni casi i siti web non possono utilizzare le CDN di immagini. In questi casi, un componente immagine deve supportare le immagini self-hosted. Il componente Image di Next.js utilizza un ottimizzatore di immagini come server di immagini integrato che fornisce un'API simile a CDN. L'ottimizzatore utilizza Sharp per le trasformazioni delle immagini di produzione se è installato sul server. Questa libreria è una buona scelta per chiunque voglia creare la propria pipeline di ottimizzazione delle immagini.
Supporto del caricamento progressivo
Il caricamento progressivo è una tecnica utilizzata per mantenere vivo l'interesse degli utenti mostrando un'immagine segnaposto, in genere di qualità notevolmente inferiore, durante il caricamento dell'immagine effettiva. Migliora le prestazioni percepite e l'esperienza utente. Può essere utilizzato in combinazione con il caricamento lento per le immagini below the fold o above the fold.
Il componente Image di Next.js supporta il caricamento progressivo dell'immagine tramite la proprietà placeholder. Può essere utilizzato come LQIP (segnaposto immagine di bassa qualità) per mostrare un'immagine sfocata o di bassa qualità mentre viene caricata l'immagine effettiva.
Impatto
Con tutte queste ottimizzazioni incorporate, abbiamo ottenuto ottimi risultati con il componente Image di Next.js in produzione e stiamo lavorando anche con altri tech stack su componenti immagine simili.
Quando Leboncoin ha eseguito la migrazione del frontend JavaScript precedente a Next.js, ha eseguito anche l'upgrade della pipeline di immagini per utilizzare il componente Image di Next.js. In una pagina migrata da <img>
a successiva/immagine, il valore LCP è diminuito da 2,4 a 1,7 secondi. I byte totali delle immagini scaricati per la pagina sono passati da 663 kB a 326 kB (con circa 100 kB di byte di immagini con caricamento differito).
Lezioni apprese
Chiunque crei un'app Next.js può trarre vantaggio dall'utilizzo del componente Image di Next.js per l'ottimizzazione. Tuttavia, se vuoi creare astrazioni del rendimento simili per un altro framework o CMS, di seguito sono riportate alcune lezioni che abbiamo imparato nel corso del tempo che potrebbero esserti utili.
Le valvole di sicurezza possono causare più danni che bene
In una versione precedente del componente Image di Next.js, abbiamo fornito un attributo unsized
che consentiva agli sviluppatori di bypassare il requisito relativo alle dimensioni e di utilizzare immagini con dimensioni non specificate. Abbiamo ritenuto che questa opzione fosse necessaria nei casi in cui fosse impossibile conoscere in anticipo l'altezza o la larghezza dell'immagine. Tuttavia, abbiamo notato che gli utenti consigliano l'attributo unsized
nei problemi di GitHub come soluzione universale per i problemi relativi al requisito di dimensionamento, anche nei casi in cui potrebbero risolvere il problema in modi che non peggiorano il CLS. In seguito, abbiamo ritirato e rimosso l'attributo unsized
.
Separare le frizioni utili dalle seccature inutili
Il requisito per il dimensionamento di un'immagine è un esempio di "grazie utile". Limita l'utilizzo del componente, ma offre in cambio vantaggi eccezionali in termini di prestazioni. Gli utenti accetteranno facilmente il vincolo se hanno un quadro chiaro dei potenziali vantaggi in termini di rendimento. Pertanto, vale la pena spiegare questo compromesso nella documentazione e in altro materiale pubblicato sul componente.
Tuttavia, puoi trovare soluzioni alternative a questo problema senza sacrificare le prestazioni. Ad esempio, durante lo sviluppo del componente Image di Next.js, abbiamo ricevuto dei reclami in merito alla difficoltà di cercare le dimensioni delle immagini memorizzate localmente. Abbiamo aggiunto le importazioni di immagini statiche, che semplificano questo processo recuperando automaticamente le dimensioni delle immagini locali in fase di compilazione utilizzando un plug-in Babel.
Trova il giusto equilibrio tra funzionalità di praticità e ottimizzazioni delle prestazioni
Se il componente immagine non fa altro che imporre "attriti utili" agli utenti, gli sviluppatori tenderanno a non volerlo utilizzare. Abbiamo riscontrato che, sebbene le funzionalità di rendimento come le dimensioni delle immagini e la generazione automatica dei valori srcset
fossero le più importanti, Anche le funzionalità di praticità rivolte agli sviluppatori, come il caricamento lento automatico e i segnaposto sfocati integrati, hanno suscitato l'interesse per il componente Image di Next.js.
Definire una roadmap per le funzionalità che ne promuovono l'adozione
È molto difficile creare una soluzione che funzioni perfettamente in tutte le situazioni. Potresti avere la tentazione di progettare qualcosa che funzioni bene per il 75% delle persone e poi dire all'altro 25% che "in questi casi, questo componente non fa per te".
In pratica, questa strategia risulta in contrasto con i tuoi obiettivi di designer di componenti. Vuoi che gli sviluppatori adottino il tuo componente per trarre vantaggio dal suo rendimento. È difficile farlo se esiste un gruppo di utenti che non è in grado di eseguire la migrazione e si sente escluso dalla conversazione. È probabile che esprimano delusione, il che genera percezioni negative che influiscono sull'adozione.
È consigliabile avere una roadmap per il componente che copra tutti i casi d'uso ragionevoli a lungo termine. Inoltre, è utile spiegare in modo esplicito nella documentazione ciò che non è supportato e perché, in modo da definire le aspettative relative ai problemi che il componente intende risolvere.
Conclusione
L'utilizzo e l'ottimizzazione delle immagini sono complessi. Gli sviluppatori devono trovare il giusto equilibrio tra rendimento e qualità delle immagini, garantendo al contempo un'esperienza utente ottimale. Ciò rende l'ottimizzazione delle immagini un'impresa ad alto costo e ad alto impatto.
Invece di lasciare che ogni app reinventarsi ogni volta, abbiamo creato un modello di best practice che sviluppatori, framework e altri stack tecnici potessero utilizzare come riferimento per le proprie implementazioni. Questa esperienza sarà davvero utile man mano che supporteremo altri framework per i relativi componenti immagine.
Il componente Next.js Image ha migliorato con successo i risultati delle prestazioni nelle applicazioni Next.js, migliorando così l'esperienza utente. Riteniamo che sia un ottimo modello che funzionerebbe bene nell'ecosistema più ampio e ci piacerebbe conoscere il parere degli sviluppatori che vorrebbero adottare questo modello nei loro progetti.