Un componente immagine incapsula 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 delle prestazioni per le applicazioni web e un'area di interesse chiave per l'ottimizzazione. Le immagini non ottimizzate contribuiscono al gonfiore della pagina e rappresentano oltre il 70% del peso totale della pagina in byte al 90th 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 collaborato con 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 stack tecnologici. Abbiamo collaborato a un componente simile per Nuxt.js e stiamo lavorando con Angular all'ottimizzazione delle immagini nelle versioni future. Questo post descrive la progettazione del componente immagine Next.js e le lezioni che abbiamo appreso durante il percorso.

Problemi e opportunità di ottimizzazione delle immagini
Le immagini non influiscono solo sul rendimento, ma anche sull'attività. Il numero di immagini in una pagina è stato il secondo miglior indicatore di 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. Lighthouse elenca diverse opportunità per ottimizzare le immagini e migliorare i web vital nell'ambito del controllo delle best practice. 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 danneggiano il 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 sugli elementi <img> può contribuire a evitare gli spostamenti 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 a quelle naturali. Una differenza significativa nelle proporzioni può comportare la distorsione dell'immagine. Una proprietà relativamente nuova che consente di specificare le proporzioni in CSS può contribuire a dimensionare le immagini in modo reattivo, evitando il CLS.
Le immagini di grandi dimensioni possono influire negativamente sull'LCP
Maggiore è la dimensione del file di un'immagine, più tempo ci vorrà per scaricarla. Un'immagine di grandi dimensioni potrebbe essere l'immagine "hero" della pagina o l'elemento più significativo nell'area visibile responsabile dell'attivazione di Largest Contentful Paint (LCP). Un'immagine che fa parte dei contenuti critici e richiede molto tempo per il download ritarderà l'LCP.
In molti casi, gli sviluppatori possono ridurre le dimensioni delle immagini tramite una migliore compressione e l'utilizzo di immagini responsive. Gli attributi srcset e sizes dell'elemento <img> consentono di fornire file immagine di dimensioni diverse. Il browser può quindi scegliere quella giusta in base alle dimensioni e alla risoluzione dello schermo.
Una compressione delle immagini scadente può influire negativamente sull'LCP
I formati delle 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 del 25-50% in alcuni casi, mantenendo la stessa qualità dell'immagine. Questa riduzione comporta download più veloci con un consumo di dati inferiore. L'app deve mostrare formati delle immagini moderni ai browser che li supportano.
Il caricamento di immagini non necessarie influisce negativamente sull'LCP
Le immagini sotto la piega o non visibili nella finestra non vengono mostrate all'utente quando la pagina viene caricata. Possono essere posticipati in modo da non contribuire all'LCP e ritardarlo. Il caricamento lento può essere utilizzato per caricare queste immagini in un secondo momento, man mano che l'utente scorre verso il basso.
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, spesso questo non accade nella pratica e le immagini inefficienti continuano a rallentare il web. Di seguito sono elencati alcuni possibili motivi:
- Priorità: gli sviluppatori web tendono a concentrarsi sull'ottimizzazione di codice, JavaScript e dati. Pertanto, potrebbero non essere a conoscenza di problemi con le immagini o di come ottimizzarle. Le immagini create da designer o caricate dagli utenti potrebbero non essere in cima all'elenco delle priorità.
- Soluzione pronta all'uso: anche se gli sviluppatori sono consapevoli delle sfumature dell'ottimizzazione delle immagini, l'assenza di una soluzione all-in-one pronta all'uso per il loro framework o stack tecnologico può essere un deterrente.
- Immagini dinamiche: oltre alle immagini statiche che fanno parte dell'applicazione, le immagini dinamiche vengono caricate dagli utenti o estratte da database o CMS esterni. Potrebbe essere difficile definire le dimensioni di queste immagini quando la fonte è dinamica.
- Sovraccarico di markup: le soluzioni per includere le dimensioni dell'immagine o
srcsetper 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 del browser: i formati delle immagini moderni come AVIF e WebP creano file immagine 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: esistono diverse tecniche e librerie disponibili per implementare il caricamento lento delle immagini visibili sotto la piega. Scegliere quello migliore può essere difficile. Gli sviluppatori potrebbero anche non conoscere la distanza migliore dalla "piega" per caricare le immagini differite. Le diverse dimensioni delle finestre sui dispositivi possono complicare ulteriormente la situazione.
- Scenario in evoluzione: man mano che i browser iniziano a supportare nuove funzionalità HTML o CSS per migliorare il rendimento, gli sviluppatori potrebbero avere difficoltà a 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. Nel complesso, gli sviluppatori troverebbero più semplice valutare e implementare questi miglioramenti 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 relativo 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 tenta di risolvere i problemi relativi alle immagini in modo generico attraverso un ricco insieme di funzionalità e principi. Include anche opzioni che consentono agli sviluppatori di personalizzarlo per vari requisiti delle immagini.
Protezione dai cambiamenti di layout
Come discusso in precedenza, le immagini senza dimensioni causano spostamenti del layout e contribuiscono al CLS. Quando utilizzano il componente immagine Next.js, gli sviluppatori devono fornire le dimensioni dell'immagine utilizzando gli attributi width e height per evitare spostamenti del layout. Se le dimensioni non sono note, gli sviluppatori devono specificare layout=fill per pubblicare un'immagine senza dimensioni all'interno di un contenitore dimensionato. In alternativa, puoi utilizzare le importazioni di immagini statiche per recuperare le dimensioni dell'immagine effettiva sul disco rigido al momento della creazione e includerle 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 dimensioni, la progettazione garantisce che si prendano il tempo necessario per considerare le dimensioni delle immagini e impedire 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 sforzo con il componente Immagine. Abbiamo progettato il componente immagine Next.js per 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 ideato una soluzione in tre parti:
- Proprietà
deviceSizes: questa proprietà può essere utilizzata per configurare i punti di interruzione una sola volta in base ai dispositivi comuni alla base utenti dell'applicazione. I valori predefiniti per i punti di interruzione sono inclusi nel file di configurazione. - Proprietà
imageSizes: anche questa è una proprietà configurabile utilizzata per ottenere le dimensioni delle immagini corrispondenti ai punti di interruzione delle dimensioni del dispositivo. - Attributo
layoutin ogni immagine: viene utilizzato per indicare come utilizzare le proprietàdeviceSizeseimageSizesper ogni immagine. I valori supportati per la modalità di layout sonofixed,fill,intrinsiceresponsive.
Quando viene richiesta un'immagine con le modalità di layout adattabile o riempimento, 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à 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 dello smartphone |
|---|---|
| Layout = Intrinseco: si ridimensiona per adattarsi alla larghezza del contenitore nelle aree visibili più piccole. Non viene scalata oltre le dimensioni intrinseche dell'immagine in un'area visibile più grande. La larghezza del contenitore è al 100% | |
|
|
| Layout = Fisso: l'immagine non è reattiva. La larghezza e l'altezza sono fisse, in modo simile all'elemento ` |
|
|
|
| Layout = Responsive: ridimensiona in base alla larghezza del contenitore in diverse finestre visualizzazione, mantenendo le proporzioni. | |
|
|
Layout = Riempi: larghezza e altezza vengono allungate per riempire il contenitore principale. (In questo esempio, la larghezza dell'elemento principale <div> è impostata su 300*500)
|
|
|
|
Fornire il caricamento lento integrato
Il componente Immagine fornisce una soluzione di caricamento differito integrata e performante come impostazione predefinita. Quando utilizzi l'elemento <img>, esistono alcune opzioni per il caricamento differito, ma tutte presentano degli 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 di caricamento lento personalizzata richiede impegno e una progettazione e un'implementazione ponderate. Gli sviluppatori potrebbero non avere sempre il tempo per farlo.
- Importa una libreria di terze parti per caricare le immagini in modo differito: potrebbe essere necessario un ulteriore impegno per valutare e integrare una libreria di terze parti adatta al caricamento differito.
Nel componente immagine Next.js, il caricamento è impostato su "lazy" per impostazione predefinita. Il caricamento differito viene implementato utilizzando Intersection Observer, che è disponibile nella maggior parte dei browser moderni. Gli sviluppatori non sono tenuti a fare nulla di aggiuntivo per abilitarlo, ma possono disattivarlo quando necessario.
Precaricare le immagini importanti
Molto spesso, gli elementi LCP sono immagini e le immagini di grandi dimensioni possono ritardare l'LCP. È consigliabile precaricare le immagini critiche in modo che il browser possa rilevarle prima. Quando utilizzi un elemento <img>, puoi includere un suggerimento di precaricamento nell'intestazione HTML come segue.
<link rel="preload" as="image" href="important.png">
Un componente immagine ben progettato deve offrire un modo per modificare la sequenza di caricamento delle immagini, indipendentemente dal framework utilizzato. Nel caso del componente immagine Next.js, gli sviluppatori possono indicare un'immagine che è un buon candidato per il precaricamento 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 immagine possono anche esplorare le opzioni per applicare l'euristica per automatizzare il precaricamento delle immagini visibili nella parte superiore della pagina che soddisfano criteri specifici.
Incoraggiare l'hosting di immagini ad alte prestazioni
Le CDN per le immagini sono consigliate per automatizzare l'ottimizzazione delle immagini e supportano anche i formati delle immagini moderni come WebP e AVIF. Il componente immagine Next.js utilizza una CDN per le immagini per impostazione predefinita utilizzando 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 concatenerà 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 fornitore di servizi cloud personalizzato implementando una funzione loader personalizzata per l'app.
Supportare le immagini self-hosted
Potrebbero verificarsi situazioni in cui i siti web non possono utilizzare le CDN di immagini. In questi casi, un componente immagine deve supportare le immagini self-hosted. Il componente immagine Next.js utilizza un ottimizzatore di immagini come server di immagini integrato che fornisce un'API simile a una 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.
Supportare il caricamento progressivo
Il caricamento progressivo è una tecnica utilizzata per mantenere vivo l'interesse degli utenti visualizzando un'immagine segnaposto di qualità generalmente molto inferiore mentre viene caricata l'immagine effettiva. Migliora le prestazioni percepite e l'esperienza utente. Può essere utilizzato in combinazione con il caricamento differito per le immagini sotto la piega o sopra la piega.
Il componente immagine Next.js supporta il caricamento progressivo dell'immagine tramite la proprietà placeholder. Può essere utilizzato come LQIP (segnaposto per immagini di bassa qualità) per visualizzare un'immagine sfocata o di bassa qualità durante il caricamento dell'immagine effettiva.
Impatto
Con tutte queste ottimizzazioni incorporate, abbiamo riscontrato un buon successo con il componente immagine Next.js in produzione e stiamo lavorando anche con altri stack tecnologici su componenti immagine simili.
Quando Leboncoin ha eseguito la migrazione del frontend JavaScript legacy a Next.js, ha anche eseguito l'upgrade della pipeline di immagini per utilizzare il componente immagine Next.js. In una pagina di cui è stata eseguita la migrazione da <img> a successivo/immagine, LCP è diminuito da 2,4 secondi a 1,7 secondi. Il totale dei byte delle immagini scaricati per la pagina è passato da 663 kB a 326 kB (con circa 100 kB di byte delle immagini caricati in modalità differita).
Lezioni apprese
Chiunque crei un'app Next.js può trarre vantaggio dall'utilizzo del componente immagine Next.js per l'ottimizzazione. Tuttavia, se vuoi creare astrazioni di rendimento simili per un altro framework o CMS, di seguito sono riportate alcune lezioni che abbiamo appreso lungo il percorso e che potrebbero esserti utili.
Le valvole di sicurezza possono causare più danni che benefici
In una release iniziale del componente immagine Next.js, abbiamo fornito un attributo unsized che consentiva agli sviluppatori di ignorare il requisito di dimensionamento e utilizzare immagini con dimensioni non specificate. Abbiamo pensato che questa opzione fosse necessaria nei casi in cui era 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 ai problemi con il requisito di dimensionamento, anche nei casi in cui potrebbero risolvere il problema in modi che non peggiorano il CLS. Successivamente, abbiamo ritirato e rimosso l'attributo unsized.
Distinguere l'attrito utile da quello inutile
Il requisito di ridimensionamento di un'immagine è un esempio di "attrito utile". Limita l'utilizzo del componente, ma in cambio offre vantaggi di prestazioni eccezionali. Gli utenti accetteranno facilmente il vincolo se avranno un quadro chiaro dei potenziali vantaggi in termini di rendimento. Pertanto, è utile spiegare questo compromesso nella documentazione e in altro materiale pubblicato sul componente.
Tuttavia, puoi trovare soluzioni alternative per questi problemi senza compromettere il rendimento. Ad esempio, durante lo sviluppo del componente immagine Next.js, abbiamo ricevuto lamentele 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.
Trovare un equilibrio tra funzionalità pratiche e ottimizzazioni delle prestazioni
Se il componente immagine non fa altro che imporre una "frizione utile" ai suoi utenti, gli sviluppatori tenderanno a non utilizzarlo. Abbiamo scoperto che, sebbene le funzionalità di rendimento come il dimensionamento 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 interesse per il componente immagine Next.js.
Definisci una roadmap per le funzionalità per favorirne l'adozione
Creare una soluzione che funzioni perfettamente in tutte le situazioni è molto difficile. Può essere allettante progettare qualcosa che funzioni bene per il 75% delle persone e poi dire al restante 25% che "in questi casi, questo componente non fa per te".
In pratica, questa strategia si rivela in contrasto con i tuoi obiettivi di progettazione dei componenti. Vuoi che gli sviluppatori adottino il tuo componente per trarre vantaggio dai suoi vantaggi in termini di rendimento. È difficile farlo se c'è un gruppo di utenti che non può eseguire la migrazione e si sente escluso dalla conversazione. È probabile che esprimano delusione, il che porta a percezioni negative che influiscono sull'adozione.
È consigliabile avere una roadmap per il componente che copra tutti i casi d'uso ragionevoli nel lungo periodo. Inoltre, è utile specificare nella documentazione cosa non è supportato e perché, in modo da definire le aspettative sui problemi che il componente intende risolvere.
Conclusione
L'utilizzo e l'ottimizzazione delle immagini sono complicati. Gli sviluppatori devono trovare il giusto equilibrio tra prestazioni e qualità delle immagini, garantendo al contempo un'ottima esperienza utente. Ciò rende l'ottimizzazione delle immagini un'attività costosa e di grande impatto.
Invece di far reinventare la ruota a ogni app, abbiamo creato un modello di best practice che sviluppatori, framework e altri stack tecnologici potrebbero utilizzare come riferimento per le proprie implementazioni. Questa esperienza si rivelerà preziosa man mano che supporteremo altri framework sui relativi componenti immagine.
Il componente immagine di Next.js 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 ricevere feedback dagli sviluppatori che vorrebbero adottarlo nei loro progetti.