Performanceproblemen bij websites herkennen en verhelpen
Bezoekers van je website waarderen alle interactieve elementen, fraaie animaties, webfonts, video’s en high-res foto’s natuurlijk, maar een op die manier opgetuigde website laadt vaak wel traag. Het weer vlot trekken van een langzame website is als een meerkamp met uiteenlopende disciplines – een overzicht.
Een gemiddelde webpagina neemt tegenwoordig zo’n twee megabyte data in beslag, die worden verdeeld over 75 HTTP-requests (zie de link op de laatste pagina van dit artikel). De browser moet bijna een halve megabyte aan JavaScript-code verteren. Tegelijkertijd zijn de gebruikers niet meer zo geduldig als in de tijd van het inbelmodem: een drie seconden leeg scherm is voor sommige bezoekers al te veel. Een website die na tien seconden nog niets laat zien, is het merendeel van zijn bezoekers kwijt.
Er zijn veel verschillende maatregelen die je als website-eigenaar kunt nemen om je pagina’s sneller te maken. Dit artikel beschrijft optimalisaties voor het front-end. Het geeft een overzicht van het spectrum aan mogelijkheden en gaat alleen in bepaalde gevallen ook de diepte in. De implementatie in detail hangt daarbij sterk af van de eisen en problemen van de betreffende website.
LEVEL 0: TESTMIDDELEN
Het eerste wat je moet doen, is uitzoeken waar het probleem zit. Tegenwoordig worden Google PageSpeed Insights (PSI), Webpagetest.org en Lighthouse het meest gebruikt voor tests en tips. PSI is relatief duidelijk en is zeer geschikt voor beginners. Het opensourceproject Webpagetest.org is meer gericht op het presenteren van ruwe gegevens dan op duidelijke instructies waar je iets mee kunt.
Lighthouse – ook opensource – komt net als PSI van Google, maar test niet alleen de prestaties van een website, maar ook de SEO en toegankelijkheid. Het zit achter de analysefuncties van PSI, maar beoordeelt anders. Lighthouse is geen webservice, je kunt het vinden bij de Chrome-ontwikkelaarstools, maar je kunt het ook installeren als een Node.jsapplicatie.
De meetresultaten geven enige aanwijzingen, maar je moet ze niet overschatten – ze zijn vaak afhankelijk van toevalligheden en verschillen tussen bijvoorbeeld Lighthouse en PSI. Zelfs voor Googles eigen pagina’s pakt de snelheidsindex soms slecht uit. Nuttiger zijn de adviezen (‘Remove unused JavaScript’, ‘Avoid long main-thread tasks’, enzovoorts), in combinatie met specifieke details over de betrokken bestanden en regels.
Afgezien van Lighthouse staan er bij de ontwikkelaarstools van de populaire browsers meer hulpmiddelen voor het meten van netwerktoegang, renderprestaties en het gebruik van hulpbronnen. Die registreren grote hoeveelheden data als je ze activeert, die je vervolgens kunt bestuderen om knelpunten in de prestaties op te sporen. Ze zijn echter nauwelijks geschikt voor beginners op het gebied van website-tuning.
LEVEL 1: AFSLANKEN
Met tools voor browserontwikkelaars kun je de datasnelheid beperken, bijvoorbeeld om mobiel gebruik na te bootsen. Als je dat eenmaal hebt geprobeerd, zul je meer gemotiveerd zijn om je website op te schonen en te comprimeren.
Afbeeldingen hebben meestal de grootste besparingspotentie – geen enkele andere maatregel werkt zo snel als het optimaliseren daarvan. Het is duidelijk dat een afbeelding niet groter mag zijn dan de maximale breedte van het scherm. Wat de zaak nog ingewikkelder maakt, zijn de Retina-schermen die beelden kunnen schalen naar hogere resoluties. Een iPhone geeft met de standaardschaling elke CSSpixel bijvoorbeeld weer op 2,2 apparaatpixels. Een 500×300 pixels grote afbeelding zal er goed uitzien in een CSS-container van die grootte, maar het apparaat zou ook 1000 × 600 pixels in die ruimte kunnen weergeven – de afbeelding ziet er dan gewoon scherper uit.
Om met dergelijke gevallen en verschillende beeldformaten om te gaan via een responsive layout, hebben front-end-ontwikkelaars CSS-mediaquery’s en vooral de HTML-attributen srcset en sizes tot hun beschikking. De browser gebruikt die om te bepalen welk afbeeldingsbestand het beste past en downloadt alleen dat bestand, bijvoorbeeld:
Een JPEG-kwaliteitsniveau van meer dan 80 of een verliesvrij gecomprimeerde PNG afbeelding is meestal een verspilling van bandbreedte. Het verwijderen van metadata of een efficiëntere compressie zal ook heel wat kilobytes besparen. Dat kun je doen met gewone beeldbewerkings- en weergavesoftware of met console-gereedschappen zoals jpegtran, jpegoptim en optipng. Die hulpmiddelen kunnen grote hoeveelheden afbeeldingen verwerken en kunnen worden geïntegreerd in de build-pipeline. De volgende instructie verkleint sommige foto’s tot een tiende van hun bestandsgrootte (pas op, overschrijft de bronbestanden!): jpegoptim -o -m75 --strip-all --all-progressive *.jpg
Voor JPEG’s wordt progressieve rendering aanbevolen, waarbij het beeld vanaf het begin op ware grootte verschijnt en gedetailleerder wordt naarmate het laadt – dat voelt voor een gebruiker sneller aan. Voor pictogrammen worden tegenwoordig vectorafbeeldingen in de vorm van SVG’s of pictogramlettertypes gebruikt. PNG’s zijn vooral interessant voor transparantie.
Het nieuwe WebP-formaat beslaat slechts ongeveer 80 tot 90 procent van een equivalent JPEGbestand, maar je hebt eventueel een fall-back nodig voor Internet Explorer. Tot nu toe werkt alleen Chrome met AVIF, dat zijn sterke punten uitspeelt bij hoge compressieverhoudingen en GIF-achtige animaties mogelijk maakt.
Voor video doen veel sites een beroep op externe dienstverleners die bij het streamen de afspeelkwaliteit aanpassen aan de bandbreedte. Maar waar een
Je moet ook websitecode comprimeren. Codereductietools bestaan voor CSS en HTML, maar dat levert meer op bij JavaScript. Het populairste hulpmiddel daarvoor heet Uglify – de uitvoer ervan is nauwelijks leesbaar voor mensen, maar voor de computer maakt dat niet uit.
Het is moeilijker, maar lonender, om overbodige code er helemaal uit te gooien. JavaScript-bibliotheken laten de code enorm groeien. Daarom moet je je bij elk script van derden afvragen: heb ik dat echt nodig? Moet ik moments.js toevoegen als ik één keer een datum converteer? Is de carrousel-plug-in de moeite waard, heb ik jQuery echt nodig omdat $(...) zo lekker kort is?
Niet te vergeten: de browser is niet klaar na het downloaden, hij moet de code ook verwerken. Terwijl dat bij afbeeldingen een kwestie van milliseconden is, is het harder werken bij JavaScript, dat de hoofdthread vaak secondenlang blokkeert. Op een apparaat met weinig rekenkracht kan het compileren en uitvoeren langer duren dan het downloaden. Tests met echte hardware bij verschillende netwerkkwaliteiten leveren soms verrassende resultaten op, maar zijn tijdrovend.
Bij projecten die in de loop der jaren steeds gegroeid zijn, kom je vaak een verbazingwekkende warboel aan code tegen, zoals verschillende jQuery-versies of polyfills die eigenlijk al jaren niemand meer nodig heeft. Maar test de site grondig en gooi er niet overhaast code uit! Een JavaScript-exception stopt
dan bijvoorbeeld verdere uitvoering van de code, en als die niet meer werkt is zelfs de beste optimalisatie nutteloos.
Chrome heeft bij de ontwikkelaarstools het tabblad Coverage (in het menu onder ‘More tools’) dat ongebruikte CSS-selectors en JavaScript-functies rood markeert. Bij de meeste websites het aandeel daarvan ver boven de 50 procent. De Node.js-tool UnCSS geeft de werkelijk gebruikte CSS weer – zelfs overkoepelend voor meerdere pagina’s en schermformaten.
Bij het importeren van modules is het vaak mogelijk om dat te beperken tot afzonderlijke componenten. Moderne bundlers zoals webpack en Rollup kunnen overweg met die ‘tree-shaking’ en kopiëren met import {func1} from ‘bigFile.js’ alleen de code die bij func1 hoort naar het project in plaats van het hele scriptbestand.
LEVEL 2: OVERDRACHT
Ondanks kleine verbeteringen heeft het netwerkprotocol HTTP zijn wortels in het begin van de jaren negentig, en TCP, waar het op gebaseerd is, is nog ouder. Beide doen hun werk degelijk, maar een beetje omslachtig – ze zijn eigenlijk niet bedacht voor de veel voorkomende scenario’s van vandaag de dag met vaak meer dan honderd requests per paginaaanroep.
De door beide protocollen veroorzaakte overhead is vooral nadelig bij het versturen van kleine bestanden. Daarom wordt het als een performanceoptimalisatie beschouwd om kleine datapakketten te combineren tot grotere – bijvoorbeeld door verschillende script- en stylesheetbestanden te bundelen (bundling) of door trucs zoals CSS-sprites, waarbij alle pictogramafbeeldingen in één afbeelding worden gepropt om er met behulp van CSS de juiste uit te pikken.
Volgens de HTTP-specificatie beperken browsers bovendien het aantal gelijktijdige verbindingen met dezelfde host. Doorgaans staan ze zes gelijktijdige downloads toe. Om dat te omzeilen, gebruiken sommige sites domain-sharding – het verdelen van bronnen over meerdere subdomeinen.
HTTP/2 elimineert de noodzaak voor dergelijke hacks. Er is slechts één TCP-verbinding nodig om een willekeurig aantal HTTP-responses terug te sturen – zelfs responses waar de client nog niet om heeft gevraagd (server-push). HTTP/2 is ondertussen een gevestigde norm, die volgens W3Techs bij 45 procent van alle websites gebruikt wordt [1].
Het protocol wordt inderdaad gebruikt bij internationale websites zoals Google, Facebook, Amazon, eBay, LinkedIn en hun content-delivery-networks (CDN). Zelfs sommige kant-en-klare shared-hosting aanbieders bieden HTTP/2, terwijl andere hosters tot nu toe nog niet zijn overgestapt. Je moet echter geen wonderen verwachten van HTTP/2.
De snelste download is natuurlijk de download die niet gedaan hoeft te worden. Slim cachen kan het herhaaldelijk laden van pagina’s enorm versnellen en er zelfs voor zorgen dat bezoekers iets zien als ze offline zijn. Gebruik daar de HTTP-headers CacheControl of Expires voor. Daarbij kun je differentiëren naar content: de browser mag een afbeelding bijvoorbeeld een maand in de cache bewaren, terwijl de stylesheet slechts twee uur geldig blijft. De startpagina moet daarentegen al na korte tijd (bijvoorbeeld 30 seconden) opnieuw worden opgevraagd.
Als er ook een ETag-header is ingesteld, kunnen de browser en de server vergelijken of ze beide dezelfde bestandsversie hebben. In dat geval ant
woordt de server met een 304-code zonder data over te dragen.
De programmeerbare front-end-cache die mogelijk is met Progressive Web Apps (PWA) gaat een stap verder. Het belangrijkste doel is om websites offline beschikbaar te maken op mobiele apparaten, maar prestatie-optimalisatie voor desktopbrowsers werkt er net zo goed mee. Maar of het nu gaat om PWA of cache-instellingen: overdrijf het niet, anders ziet de bezoeker te lang een verouderde versie van de website!
LEVEL 3: VOOR- EN NALEVERING
Als je de download eenmaal gereduceerd hebt, kun je nadenken over wanneer je bepaalde bronnen nodig hebt. Het standaardgedrag – een HTML-bestand haalt alle bijbehorende scripts, stijlen en afbeeldingen van internet als het wordt geladen – is meestal niet het snelst. Sommige dingen kunnen beter vooraf worden opgevraagd, andere later.
Maar wat betekent ‘snelheid’ eigenlijk voor een webpagina? Je kunt de tijd meten die verstrijkt tussen de eerste request en het arriveren van het laatste bit, maar dat is niet noodzakelijk de relevante variabele. Gebruikers zijn meer geïnteresseerd in drie andere gebeurtenissen: dat er iets op het scherm verschijnt, dat ze al een soort lay-out in de browserviewport zien, en dat ze met die view kunnen interageren.
Die gebeurtenissen zijn de First Contentful Paint (FCP), de Largest Content Paint (LCP) of de First Meaningful Paint (FMP) – en de Time to Interactive (TTI).
Als het dus vijf seconden duurt om een pagina te laden, moet de gebruiker tot dat moment niet naar een wit scherm hoeven te staren. In het ideale geval zien ze de relevante inhoud binnen een seconde, met weinig verandering daarna, en kunnen ze de pagina bedienen terwijl de browser nog bezig is met het naladen van afbeeldingen, video’s en interacties onder het weergavevenster.
Meestal vormen afbeeldingen het grootste deel van de data, en daarom is lazy-loading ingeburgerd – de browser vraagt de afbeeldingen alleen op wanneer hij er tijd voor heeft of ze nodig heeft. Moderne browsers (met uitzondering van Safari) hebben daar geen JavaScript meer voor nodig: een loading="lazy" in de is voldoende. Dat werkt ook voor iFrames.
Het grootste probleem is de inhoud die de eerste rendering blokkeert: JavaScript-code die in de header is ingebed en de stylesheetbestanden. Als de browser dergelijke inhoud tegenkomt, stopt hij het laden van de pagina, downloadt het bestand en parst het of voert het uit, alvorens verder te gaan met het renderen.
Er moeten zo weinig mogelijk scripts draaien voordat de pagina gerenderd wordt. Dus verplaats je