Bouwen aan een effectieve beeldcomponent

Een afbeeldingscomponent bevat best practices voor prestatieoptimalisatie en biedt een kant-en-klare oplossing voor het optimaliseren van afbeeldingen.

Leena Sohoni
Leena Sohoni
Kara Erickson
Kara Erickson
Alex Castle
Alex Castle

Afbeeldingen vormen een veelvoorkomende bron van prestatieknelpunten voor webapplicaties en een belangrijk aandachtspunt voor optimalisatie. Niet-geoptimaliseerde afbeeldingen dragen bij aan paginagrootte en zijn verantwoordelijk voor meer dan 70% van het totale paginagewicht in bytes bij het 90e percentiel. Meerdere manieren om afbeeldingen te optimaliseren vereisen een intelligente "afbeeldingscomponent" met ingebouwde prestatieoplossingen.

Het Aurora- team werkte samen met Next.js om een ​​dergelijke component te bouwen. Het doel was om een ​​geoptimaliseerde afbeeldingssjabloon te creëren die webontwikkelaars verder konden aanpassen. De component dient als een goed voorbeeld en zet een standaard voor het bouwen van afbeeldingscomponenten in andere frameworks, contentmanagementsystemen (CMS) en technologieën. We hebben samengewerkt aan een vergelijkbare component voor Nuxt.js en werken met Angular aan beeldoptimalisatie voor toekomstige versies. In dit artikel bespreken we hoe we de Next.js Image-component hebben ontworpen en de lessen die we daarbij hebben geleerd.

Afbeeldingscomponent als uitbreiding van afbeeldingen

Problemen en mogelijkheden met betrekking tot beeldoptimalisatie

Afbeeldingen hebben niet alleen invloed op de prestaties, maar ook op de bedrijfsvoering. Het aantal afbeeldingen op een pagina was de op één na belangrijkste voorspeller van conversies bij websitebezoekers. Sessies waarin gebruikers converteerden, bevatten 38% minder afbeeldingen dan sessies waarin ze niet converteerden. Lighthouse noemt diverse mogelijkheden om afbeeldingen te optimaliseren en de webstatistieken te verbeteren als onderdeel van hun audit van best practices. Enkele veelvoorkomende gebieden waar afbeeldingen de belangrijkste webstatistieken en de gebruikerservaring kunnen beïnvloeden, worden hieronder beschreven.

Afbeeldingen zonder formaat schaden CLS.

Afbeeldingen die worden weergegeven zonder dat hun afmetingen zijn gespecificeerd, kunnen lay-outinstabiliteit veroorzaken en bijdragen aan een hoge cumulatieve lay-outverschuiving ( CLS ). Het instellen van de width en height op <img> -elementen kan helpen om lay-outverschuivingen te voorkomen. Bijvoorbeeld:

<img src="flower.jpg" width="360" height="240">

De breedte en hoogte moeten zo worden ingesteld dat de beeldverhouding van de weergegeven afbeelding dicht bij de natuurlijke beeldverhouding ligt. Een significant verschil in de beeldverhouding kan ertoe leiden dat de afbeelding er vervormd uitziet. Een relatief nieuwe eigenschap waarmee je de beeldverhouding in CSS kunt specificeren, kan helpen om afbeeldingen responsief te schalen en tegelijkertijd CLS te voorkomen.

Grote afbeeldingen kunnen LCP beschadigen.

Hoe groter de bestandsgrootte van een afbeelding, hoe langer het downloaden duurt. Een grote afbeelding kan de 'hero'-afbeelding van de pagina zijn of het belangrijkste element in het zichtbare gedeelte van de pagina dat verantwoordelijk is voor het activeren van de Largest Contentful Paint ( LCP ). Een afbeelding die deel uitmaakt van de kritieke content en waarvan het downloaden lang duurt, zal de LCP vertragen.

In veel gevallen kunnen ontwikkelaars de bestandsgrootte van afbeeldingen verkleinen door betere compressie en het gebruik van responsieve afbeeldingen. De attributen srcset en sizes van het <img> `-element helpen bij het aanbieden van afbeeldingsbestanden in verschillende formaten. De browser kan vervolgens het juiste formaat kiezen, afhankelijk van de schermgrootte en -resolutie.

Slechte beeldcompressie kan LCP schaden.

Moderne afbeeldingsformaten zoals AVIF of WebP bieden een betere compressie dan de veelgebruikte JPEG- en PNG-formaten. Betere compressie reduceert de bestandsgrootte in sommige gevallen met 25% tot 50% bij behoud van dezelfde beeldkwaliteit. Deze reductie leidt tot snellere downloads met een lager dataverbruik. De app moet moderne afbeeldingsformaten aanbieden aan browsers die deze formaten ondersteunen.

Het laden van onnodige afbeeldingen is nadelig voor LCP.

Afbeeldingen die zich onder de vouwlijn bevinden of niet in het zichtbare gedeelte van de pagina vallen, worden niet aan de gebruiker getoond wanneer de pagina wordt geladen. Ze kunnen worden uitgesteld, zodat ze niet bijdragen aan de laadtijd en deze niet vertragen. Lazy loading kan worden gebruikt om dergelijke afbeeldingen later te laden, wanneer de gebruiker ernaartoe scrollt.

Uitdagingen op het gebied van optimalisatie

Teams kunnen de prestatiekosten als gevolg van de eerder genoemde problemen evalueren en best practices implementeren om deze te verhelpen. In de praktijk gebeurt dit echter vaak niet, waardoor inefficiënte afbeeldingen het web blijven vertragen. Mogelijke oorzaken hiervan zijn:

  • Prioriteiten : Webontwikkelaars richten zich doorgaans op code, JavaScript en data-optimalisatie. Daardoor zijn ze zich mogelijk niet bewust van problemen met afbeeldingen of hoe deze te optimaliseren. Afbeeldingen die door ontwerpers zijn gemaakt of door gebruikers zijn geüpload, staan ​​wellicht niet hoog op hun prioriteitenlijst.
  • Kant-en-klare oplossing : Zelfs als ontwikkelaars de nuances van beeldoptimalisatie kennen, kan het ontbreken van een alles-in-één kant-en-klare oplossing voor hun framework of technologie-stack een belemmering vormen.
  • Dynamische afbeeldingen : Naast statische afbeeldingen die deel uitmaken van de applicatie, worden dynamische afbeeldingen geüpload door gebruikers of afkomstig uit externe databases of CMS'en. Het kan lastig zijn om de afmetingen van dergelijke afbeeldingen te bepalen wanneer de bron van de afbeelding dynamisch is.
  • Markup-overload : Oplossingen voor het toevoegen van de afbeeldingsgrootte of srcset voor verschillende formaten vereisen extra markup voor elke afbeelding, wat omslachtig kan zijn. Het srcset attribuut werd geïntroduceerd in 2014, maar wordt tegenwoordig slechts door 26,5% van de websites gebruikt . Bij gebruik van srcset moeten ontwikkelaars afbeeldingen in verschillende formaten aanmaken. Tools zoals just-gimme-an-img kunnen hierbij helpen, maar moeten handmatig voor elke afbeelding worden gebruikt.
  • Browserondersteuning : Moderne afbeeldingsformaten zoals AVIF en WebP creëren kleinere afbeeldingsbestanden, maar vereisen speciale verwerking in browsers die deze formaten niet ondersteunen. Ontwikkelaars moeten strategieën gebruiken zoals contentonderhandeling of het <picture -element, zodat afbeeldingen in alle browsers worden weergegeven.
  • Complicaties bij lazy loading : Er zijn diverse technieken en bibliotheken beschikbaar om lazy loading te implementeren voor afbeeldingen die zich onder de vouw bevinden. Het kiezen van de beste kan een uitdaging zijn. Ontwikkelaars weten mogelijk ook niet wat de optimale afstand tot de "vouw" is om afbeeldingen uit te stellen. Verschillende schermformaten op apparaten kunnen dit verder compliceren.
  • Veranderend landschap : Naarmate browsers nieuwe HTML- of CSS-functies gaan ondersteunen om de prestaties te verbeteren, kan het voor ontwikkelaars lastig zijn om al deze functies te evalueren. Chrome introduceert bijvoorbeeld de functie 'Fetch Priority' als een Origin Trial . Deze kan worden gebruikt om de prioriteit van specifieke afbeeldingen op de pagina te verhogen. Over het algemeen zouden ontwikkelaars het gemakkelijker vinden als dergelijke verbeteringen op componentniveau zouden worden geëvalueerd en geïmplementeerd.

Afbeeldingscomponent als oplossing

De mogelijkheden om afbeeldingen te optimaliseren en de uitdagingen bij het individueel implementeren ervan voor elke applicatie brachten ons op het idee van een afbeeldingscomponent. Een afbeeldingscomponent kan best practices inkapselen en afdwingen. Door het <img> -element te vervangen door een afbeeldingscomponent, kunnen ontwikkelaars hun problemen met beeldprestaties beter aanpakken.

Het afgelopen jaar hebben we met het Next.js- framework gewerkt aan het ontwerpen en implementeren van hun Image-component . Deze kan als directe vervanging voor de bestaande <img> -elementen in Next.js-applicaties worden gebruikt, zoals hieronder wordt weergegeven.

// 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" />
}

De component probeert beeldgerelateerde problemen op een algemene manier aan te pakken door middel van een uitgebreide set functies en principes. Het bevat ook opties waarmee ontwikkelaars de component kunnen aanpassen aan diverse beeldvereisten.

Bescherming tegen verschuivingen in de lay-out.

Zoals eerder besproken, veroorzaken afbeeldingen zonder formaat verschuivingen in de lay-out en dragen ze bij aan CLS (Content Layout Shifting). Bij gebruik van de Next.js Image-component moeten ontwikkelaars een afbeeldingsformaat opgeven met behulp van de attributen width en height om verschuivingen in de lay-out te voorkomen. Als het formaat onbekend is, moeten ontwikkelaars layout=fill specificeren om een ​​afbeelding zonder formaat te serveren die in een container met een bepaald formaat wordt geplaatst. Als alternatief kunt u statische afbeeldingsimports gebruiken om de grootte van de daadwerkelijke afbeelding op de harde schijf tijdens het buildproces op te halen en deze in de afbeelding op te nemen.

// 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" />
}

Omdat ontwikkelaars het Image-component niet zonder formaat kunnen gebruiken, zorgt het ontwerp ervoor dat ze de tijd nemen om rekening te houden met de afmetingen van de afbeelding en verschuivingen in de lay-out te voorkomen.

Bevorder responsiviteit

Om afbeeldingen responsief te maken op verschillende apparaten, moeten ontwikkelaars de attributen srcset en sizes instellen in het <img> `-element. We wilden deze inspanning verminderen met de `Image`-component. We hebben de Next.js `Image`-component zo ontworpen dat de attribuutwaarden slechts één keer per applicatie worden ingesteld. We passen ze toe op alle instanties van de `Image`-component op basis van de lay-outmodus. We hebben een oplossing in drie stappen bedacht:

  1. De eigenschap deviceSizes : Deze eigenschap kan worden gebruikt om eenmalig breakpoints te configureren op basis van de apparaten die gangbaar zijn onder de gebruikers van de applicatie. De standaardwaarden voor breakpoints zijn opgenomen in het configuratiebestand.
  2. De eigenschap imageSizes : Dit is ook een configureerbare eigenschap die wordt gebruikt om de afbeeldingsformaten te verkrijgen die overeenkomen met de apparaatgrootte-breakpoints.
  3. layout attribuut in elke afbeelding: Dit wordt gebruikt om aan te geven hoe de eigenschappen deviceSizes en imageSizes voor elke afbeelding moeten worden gebruikt. De ondersteunde waarden voor `layout mode` zijn fixed , fill , intrinsic en responsive

Wanneer een afbeelding wordt opgevraagd met de lay-outmodi responsive of fill , identificeert Next.js de weer te geven afbeelding op basis van de grootte van het apparaat dat de pagina opvraagt ​​en stelt de srcset en sizes in de afbeelding dienovereenkomstig in.

De volgende vergelijking laat zien hoe de lay-outmodus kan worden gebruikt om de grootte van de afbeelding op verschillende schermen te regelen. We hebben een demo-afbeelding gebruikt die in de Next.js-documentatie wordt gedeeld en die op een telefoon en een standaard laptop wordt bekeken.

Laptopscherm Telefoonscherm
Indeling = Intrinsiek: Schaalt naar beneden om te passen bij de breedte van de container op kleinere schermformaten. Schaalt niet verder dan de intrinsieke grootte van de afbeelding op een groter schermformaat. De breedte van de container is 100%.
De afbeelding van de bergen wordt ongewijzigd weergegeven.Verkleinde afbeelding van de bergen
Indeling = Vast: De afbeelding is niet responsief. Breedte en hoogte zijn vast, vergelijkbaar met ` ` element ongeacht het apparaat waarop het wordt weergegeven.
De afbeelding van de bergen wordt ongewijzigd weergegeven.De afbeelding van de bergen past niet op het scherm.
Lay-out = Responsief: Schaal omlaag of omhoog afhankelijk van de breedte van de container op verschillende schermformaten, met behoud van de beeldverhouding.
De afbeelding van de bergen is vergroot om op het scherm te passen.De afbeelding van de bergen is verkleind om op het scherm te passen.
Layout = Fill: Breedte en hoogte worden uitgerekt om de oudercontainer te vullen. (De breedte van de ouder <div> is in dit voorbeeld ingesteld op 300*500)
Bergafbeelding aangepast aan een formaat van 300*500 pixelsBergafbeelding aangepast aan een formaat van 300*500 pixels
Afbeeldingen weergegeven voor verschillende lay-outs

Zorg voor ingebouwde lazy loading.

De Image-component biedt standaard een ingebouwde, performante oplossing voor lazy loading . Bij gebruik van het <img> -element zijn er een aantal opties voor lazy loading, maar deze hebben allemaal nadelen waardoor ze lastig te gebruiken zijn. Een ontwikkelaar zou een van de volgende lazy loading-benaderingen kunnen kiezen:

  • Specificeer het loading : Dit wordt ondersteund door alle moderne browsers .
  • Gebruik de Intersection Observer API : Het bouwen van een aangepaste lazy-loading oplossing vereist inspanning en een doordacht ontwerp en implementatie. Ontwikkelaars hebben hier niet altijd de tijd voor.
  • Een bibliotheek van derden importeren om afbeeldingen lazy-loading te gebruiken: Mogelijk is extra inspanning nodig om een ​​geschikte bibliotheek van derden voor lazy-loading te evalueren en te integreren.

In de Next.js Image-component is het laden standaard ingesteld op "lazy" . Lazy loading wordt geïmplementeerd met behulp van Intersection Observer, die beschikbaar is in de meeste moderne browsers . Ontwikkelaars hoeven niets extra's te doen om dit in te schakelen, maar ze kunnen het indien nodig uitschakelen.

Belangrijke afbeeldingen vooraf laden

LCP-elementen zijn vaak afbeeldingen, en grote afbeeldingen kunnen LCP vertragen. Het is daarom verstandig om kritieke afbeeldingen vooraf te laden , zodat de browser deze sneller kan detecteren. Bij gebruik van een <img> -element kan een preload-hint in de <head> van de HTML-code worden opgenomen, zoals hieronder weergegeven.

<link rel="preload" as="image" href="important.png">

Een goed ontworpen afbeeldingscomponent moet een manier bieden om de laadvolgorde van afbeeldingen aan te passen, ongeacht het gebruikte framework. In het geval van de Next.js Image-component kunnen ontwikkelaars een afbeelding die geschikt is om vooraf te laden, aangeven met behulp van het priority van de afbeeldingscomponent.

<Image src="/hero.jpg" alt="hero" height="400" width="200" priority />

Het toevoegen van een priority vereenvoudigt de markup en maakt het gebruik ervan gemakkelijker. Ontwikkelaars van afbeeldingscomponenten kunnen ook opties onderzoeken om heuristieken toe te passen voor het automatiseren van het vooraf laden van afbeeldingen die direct zichtbaar zijn op de pagina en die aan specifieke criteria voldoen.

Stimuleer hoogwaardige beeldhosting.

Afbeeldings-CDN's worden aanbevolen voor het automatiseren van beeldoptimalisatie en ondersteunen bovendien moderne afbeeldingsformaten zoals WebP en AVIF. De Next.js Image-component gebruikt standaard een afbeeldings-CDN met behulp van een loader-architectuur . Het volgende voorbeeld laat zien dat de loader configuratie van de CDN mogelijk maakt in het Next.js-configuratiebestand.

module.exports = {
  images: {
    loader: 'imgix',
    path: 'https://ImgApp/imgix.net',
  },
}

Met deze configuratie kunnen ontwikkelaars relatieve URL's in de afbeeldingsbron gebruiken, waarna het framework de relatieve URL combineert met het CDN-pad om de absolute URL te genereren. Populaire afbeeldings-CDN's zoals Imgix , Cloudinary en Akamai worden ondersteund. De architectuur ondersteunt het gebruik van een aangepaste cloudprovider door een aangepaste loader voor de app te implementeren.

Ondersteuning voor zelfgehoste afbeeldingen

Er kunnen situaties zijn waarin websites geen gebruik kunnen maken van CDN's voor afbeeldingen. In dergelijke gevallen moet een afbeeldingscomponent zelfgehoste afbeeldingen ondersteunen. De Next.js Image-component gebruikt een afbeeldingsoptimalisator als ingebouwde afbeeldingsserver die een CDN-achtige API biedt. De optimalisator gebruikt Sharp voor productiebewerkingen van afbeeldingen, mits dit op de server is geïnstalleerd. Deze bibliotheek is een goede keuze voor iedereen die zijn eigen pijplijn voor afbeeldingsoptimalisatie wil bouwen.

Ondersteuning van progressieve belasting

Progressief laden is een techniek die wordt gebruikt om de aandacht van gebruikers vast te houden door een placeholder-afbeelding weer te geven, meestal van aanzienlijk lagere kwaliteit, terwijl de daadwerkelijke afbeelding laadt. Het verbetert de waargenomen prestaties en de gebruikerservaring. Het kan worden gebruikt in combinatie met lazy loading voor afbeeldingen onder de vouw of voor afbeeldingen boven de vouw.

De Next.js Image-component ondersteunt progressief laden van de afbeelding via de placeholder- eigenschap. Dit kan worden gebruikt als een LQIP (Low-quality image placeholder) om een ​​afbeelding van lage kwaliteit of een onscherpe afbeelding weer te geven terwijl de daadwerkelijke afbeelding wordt geladen.

Invloed

Met al deze optimalisaties hebben we succes geboekt met de Next.js Image-component in productie en werken we ook aan vergelijkbare afbeeldingscomponenten voor andere technologieën.

Toen Leboncoin hun oude JavaScript-frontend migreerde naar Next.js , hebben ze ook hun image-pipeline geüpgraded om de Next.js Image-component te gebruiken. Op een pagina die migreerde van <img> naar next/image, daalde de LCP van 2,4 seconden naar 1,7 seconden. De totale hoeveelheid gedownloade afbeeldingsbytes voor de pagina daalde van 663 kB naar 326 kB (waarvan ongeveer 100 kB lazy-loaded).

Geleerde lessen

Iedereen die een Next.js-app ontwikkelt, kan profiteren van de Next.js Image-component voor optimalisatie. Maar als je vergelijkbare prestatie-abstracties wilt bouwen voor een ander framework of CMS, dan zijn de volgende lessen die we gaandeweg hebben geleerd wellicht nuttig.

Veiligheidskleppen kunnen meer kwaad dan goed doen.

In een vroege versie van de Next.js Image-component boden we een unsized attribuut aan waarmee ontwikkelaars de afmetingseis konden omzeilen en afbeeldingen met ongespecificeerde afmetingen konden gebruiken. We dachten dat dit nodig zou zijn in gevallen waarin het onmogelijk was om de hoogte of breedte van de afbeelding van tevoren te weten. We merkten echter dat gebruikers in GitHub-issues het unsized attribuut aanbevolen als een algemene oplossing voor problemen met de afmetingseis, zelfs in gevallen waarin ze het probleem op een andere manier konden oplossen die de CLS-prestaties niet verslechterde. Daarom hebben we het unsized attribuut afgekeurd en verwijderd.

Maak onderscheid tussen nuttige wrijving en zinloze ergernis.

De eis om een ​​afbeelding te schalen is een voorbeeld van "nuttige frictie". Het beperkt het gebruik van de component, maar levert daarvoor aanzienlijke prestatievoordelen op. Gebruikers zullen deze beperking gemakkelijk accepteren als ze een duidelijk beeld hebben van de potentiële prestatievoordelen. Daarom is het de moeite waard om deze afweging toe te lichten in de documentatie en andere publicaties over de component.

Er zijn echter wel oplossingen te vinden voor dergelijke problemen zonder dat dit ten koste gaat van de prestaties. Tijdens de ontwikkeling van de Next.js Image-component ontvingen we bijvoorbeeld klachten dat het lastig was om de afmetingen van lokaal opgeslagen afbeeldingen op te zoeken. We hebben daarom statische afbeeldingsimports toegevoegd, die dit proces stroomlijnen door de afmetingen van lokale afbeeldingen automatisch op te halen tijdens het buildproces met behulp van een Babel-plugin.

Vind de juiste balans tussen gebruiksgemak en prestatieoptimalisatie.

Als je afbeeldingscomponent niets anders doet dan "nuttige frictie" opleggen aan de gebruikers, zullen ontwikkelaars er over het algemeen niet mee willen werken. We ontdekten dat hoewel prestatiekenmerken zoals het aanpassen van de afbeeldingsgrootte en het automatisch genereren van srcset waarden het belangrijkst waren, ook gebruiksvriendelijke functies voor ontwikkelaars, zoals automatisch lazy loading en ingebouwde wazige placeholders, de interesse in de Next.js Image-component aanwakkerden.

Stel een routekaart op voor functies om de acceptatie ervan te bevorderen.

Het is erg moeilijk om een ​​oplossing te bouwen die in alle situaties perfect werkt. Het kan verleidelijk zijn om iets te ontwerpen dat voor 75% van de mensen goed werkt en de overige 25% vervolgens te vertellen: "In deze gevallen is dit onderdeel niet voor u geschikt."

In de praktijk blijkt deze strategie haaks te staan ​​op je doelen als componentontwerper. Je wilt dat ontwikkelaars je component gebruiken om te profiteren van de prestatievoordelen. Dit is lastig als er een groep gebruikers is die niet kan migreren en zich buitengesloten voelt. Zij zullen waarschijnlijk hun teleurstelling uiten, wat leidt tot negatieve percepties die de acceptatie belemmeren.

Het is raadzaam om een ​​roadmap voor uw component te hebben die alle redelijke gebruiksscenario's op de lange termijn dekt. ​​Het helpt ook om in de documentatie expliciet aan te geven wat niet wordt ondersteund en waarom, om zo de verwachtingen te managen over de problemen die de component beoogt op te lossen.

Conclusie

Het gebruik en de optimalisatie van afbeeldingen is complex. Ontwikkelaars moeten een balans vinden tussen prestaties en beeldkwaliteit, terwijl ze tegelijkertijd een optimale gebruikerservaring garanderen. Dit maakt beeldoptimalisatie een kostbare, maar zeer impactvolle onderneming.

In plaats van dat elke app telkens het wiel opnieuw moet uitvinden, hebben we een sjabloon met best practices ontwikkeld dat ontwikkelaars, frameworks en andere technologieën als referentie kunnen gebruiken voor hun eigen implementaties. Deze ervaring zal zeker waardevol blijken wanneer we andere frameworks ondersteunen met hun beeldcomponenten.

De Next.js Image-component heeft de prestaties van Next.js-applicaties aantoonbaar verbeterd en daarmee de gebruikerservaring geoptimaliseerd. Wij zijn ervan overtuigd dat dit een uitstekend model is dat goed zou werken binnen het bredere ecosysteem, en we horen graag van ontwikkelaars die dit model in hun projecten willen implementeren.