Logboek · Notitie 001 · Webprestaties

Waar zaten de laatste 18 punten verstopt op een statische site

De afbeeldingen waren gecomprimeerd, JavaScript stond in drie bestanden en er was geen framework. Toch gaf Lighthouse maar 82. De ontbrekende punten zaten op drie plekken die de tool wel benoemde, maar niet uitlegde.

Serhat Belen

Oprichter, BLN Global · · 7 min lezen

Kort antwoord

De afbeeldingen en JavaScript waren al op orde. De laatste punten bleven op drie plekken liggen. Eén: de controle die overbodig werk moest overslaan, las zelf de lay-out uit en liet de browser rekenen. Twee: het stijlbestand en externe lettertypen vertraagden de eerste weergave met 600 ms. We plaatsten de CSS in de pagina en hostten de lettertypen zelf. Drie: 6 van de 24 contrastwaarschuwingen waren onterecht. Een volledige scan vond daarna 13 pagina's die buiten de steekproef vielen.

Deze site gebruikt geen framework. Python-scripts maken gewone HTML-pagina’s. De stijl staat in één CSS-bestand en JavaScript in drie kleine bestanden. Afbeeldingen worden als WebP in drie formaten gemaakt. De voor de hand liggende snelheidswinst was dus al binnen.

De mobiele score bleef op 82 staan.

Na de duidelijke fouten bleken de resterende punten niet achter één groot probleem te zitten. Ze lagen verspreid over drie plekken. Om ze te vinden moest je minder naar de melding van de tool kijken en meer naar wat erachter wat je bedoelt moest je begrijpen.

Eerste plek: de controle die is geschreven om het werk over te slaan

De Lighthouse-melding forced reflow betekent dat JavaScript de browser dwingt de pagina-indeling te vroeg te berekenen. Normaal verzamelt de browser stijlwijzigingen en verwerkt hij ze in één keer. Leest JavaScript tussendoor een afmeting uit, dan moet de browser alles direct verwerken.

De waarschuwing wees naar de bronregel, maar daar stond geen zichtbare meting. Na drie rondes vonden we hem.

  • In de eerste ronde getComputedStyle calls eruit. De score bleef gelijk.
  • In ronde twee zagen we de schuldige: een stuk code dat onnodig werk moest overslaan if (window.scrollY > 0) controle. Die controle leest zelf de lay-out uit. De regel die het werk moest overslaan, liet de browser dat werk juist uitvoeren.
  • In de derde ronde verdween de functie helemaal. De beginstatus stond al in CSS. JavaScript hoefde die niet opnieuw op te bouwen.

Nog één overgebleven patroon void element.offsetWidth was. Een bekende truc die bewust een nieuwe lay-outberekening afdwingt om een overgang opnieuw te starten. In plaats daarvan twee opeenvolgende requestAnimationFrame geplaatst. Doet hetzelfde zonder de lay-out te forceren.

function herstart(fn) { /* Staat het tabblad op de achtergrond, dan draait rAF niet en blijft de demo leeg. */ if (document.hidden) { fn(); return; } requestAnimationFrame(function () { requestAnimationFrame(fn); }); }
Een overgang opnieuw starten zonder een lay-outberekening af te dwingen. Voor een achtergrondtabblad is een uitweg nodig. Daar draait rAF nooit.

De les: een prestatiewaarschuwing zegt meestal niet dat je te veel doet. Je doet het op het verkeerde moment.

Tweede plek: de 600 ms vóór de eerste paint

De tweede melding ging over bronnen die de eerste weergave blokkeerden. Bij het openen downloadde en verwerkte de browser eerst het stijlbestand en daarna de fonts. Tot die tijd verscheen er niets. De gemeten vertraging was 600 milliseconden.

We zetten de stylesheet in de pagina

Een los CSS-bestand komt bij een tweede bezoek uit de cache. Dat is winst. Bij het eerste bezoek kost het wel een extra heen-en-weerrequest, en juist dat ziet iemand die via Google binnenkomt. Bij publicatie verkleinen we de CSS en zetten we hem in de pagina. We hebben het gemeten: na Brotli was inline kleiner dan een los bestand plus een tweede request.

We hosten de fonts zelf

De Google Fonts-code verbond met twee origins: één voor de stijl fonts.googleapis.com, voor bestanden fonts.gstatic.com. Elk nieuw domein vraagt om een DNS-zoekopdracht, een TLS-handshake en een nieuwe verbinding. Dit levert het weghalen ervan meetbaar op:

250 KB165 KB

De lettertypen die een Turkse pagina downloadt. Geen 6 bestanden en 2 externe herkomsten, maar 3 bestanden vanaf één herkomst.

Hoe we maten De CSS van Google Fonts is opgehaald met een echte Chrome-identiteit, unicode-range Aan de hand van de velden haalden we de subsets eruit die de Turkse pagina echt downloadt (latin + latin-ext) en maten we de bestandsgroottes één voor één. De andere partij gebruikt in deze repo assets/fonts de echte bestandsgrootte.

Hoe de lettertypes kleiner werden en waar je betaalt zodra je naar tien talen gaat, staat in een losse notitie: tien talen, drie fontbestanden.

Derde plek: waar de tool fout zat

De toegankelijkheidscontrole gaf 24 waarschuwingen over kleurcontrast. Als we ze allemaal hadden gevolgd, was de helft van het ontwerp stukgegaan. 6 waarschuwingen klopten niet.

Reden content-visibility:auto stond aan. Deze functie stelt de verwerking van delen buiten beeld uit en versnelt zo de first paint. Precies wat we wilden. Alleen kreeg de controletool de berekende kleur van zo’n deel niet binnen. Daardoor meldde hij wit op wit, terwijl dat nergens op het scherm stond.

We maten de echte verhoudingen één voor één in de browser. 18 waarschuwingen klopten en zijn opgelost. De andere 6 waren blinde vlekken van de tool: content-visibility alleen uit de twee problematische delen verwijderd en in de rest laten staan.

Een melding van een audittool bewijst niet dat er een fout zit. Het is een bewering. Controleer die eerst en los het probleem daarna op. Op deze site liepen we nog twee keer tegen die les aan. Eén daarvan in de notitie over taalcheck Je leest het.

En de wond die we zelf maakten

Om verschuivingen te voorkomen, geef je alle afbeeldingen width en height Het attribuut is toegevoegd. Goede zet. De browser kan de plek voor de afbeelding al reserveren vóór de download. Maar de demoblokken braken: sommige afbeeldingen werden 58 pixels breed en 1500 pixels hoog getekend.

Dit is de reden: in de HTML width en height attributen zijn presentatietips en in CSS height vullen de waarde in. In het ontwerp is voor die vakken alleen aspect-ratio stond vast, height was leeg. Het attribuut kwam daar terecht en overschreef de verhouding. Fix in één regel:

img{max-width:100%;height:auto;display:block}
De attributen blijven staan en voorkomen nog steeds layout shift. CSS haalt de hoogte terug.

Waar crawlen echt helpt

De homepage stond op 100, dus het leek klaar. Niet dus. We hadden een steekproef gemeten. De site alle 150 pagina’s ging één voor één door Lighthouse. Uitkomst: 137 pagina’s scoorden op alle vier onderdelen 100, niet 13 pagina’s. Toegankelijkheid, best practices en SEO stonden op elke pagina op 100. Alleen performance week af.

Elf van de dertien pagina’s zaten in dezelfde taal: Arabisch. De laagste score was 83.

/ar/safecoast/ 83 /ar/laya/ 84 /ar/localmind/ 84 /ar/adforge/ 85 /ar/garanti/ 85 /ar/gtin/ 86 /ar/visionguard/ 86 /ar/ 89 /ar/growthos/ 97 /ar/dream/ 99 /ar/twin/ 99
De Arabische pagina’s uit de volledige scan. De twee resterende afwijkingen (/pl/modelmesh/ en /ru/terms.html, beide 98) vallen binnen de normale schommeling tussen metingen.

Lighthouse zei meteen waarom: Webfont geladen → ar.woff2. Het Arabische lettertype kwam te laat binnen en liet de pagina opnieuw opmaken. De afmetingen van de Arabische fallback lagen ver af van Noto Sans Arabic, dus de verschuiving was groot. De oplossing was dezelfde keuze die we bij Martian Mono al maakten: swap niet optional.

0,2870

Layout shift op Arabische pagina’s (CLS)

Hoe we maten Na de wijziging hebben we 15 Arabische pagina's opnieuw gemeten. Bij alle 15 was de CLS nul. De performancescores gingen van 83-89 naar 98-100, en 9 van de 15 pagina's haalden precies 100. Gemeten op de live site met Lighthouse.

De belangrijkste les hier gaat niet over performance. Een steekproef nemen en "site 100" zeggen betekent dat je iets beweert over een pagina die je niet hebt gemeten. Daardoor bleven 15 pagina's maandenlang op 83 staan. We keken er niet naar.

De grens van de uitkomst

De score is een laboratoriummeting. Die gaat uit van een vast apparaat en een vast netwerkmodel. De telefoon en verbinding van een echte bezoeker kunnen slechter zijn. Bij opeenvolgende metingen schommelt dezelfde pagina tussen 98 en 100. Een score van 100 betekent niet dat de pagina voor iedereen snel is. Het betekent dat alle meetbare obstakels weg zijn. Zodra er genoeg echte gebruikersdata zijn, kijken we naar de veldgegevens van Chrome.

Deze drie aanpassingen hadden één ding gemeen: geen ervan vroeg om meer werk. We haalden werk weg dat op het verkeerde moment draaide of helemaal niet nodig was.

Veelgestelde vragen

Wat bedoelt Lighthouse met gedwongen reflow?

Dat JavaScript de browser dwingt om verzamelde stijlwijzigingen te vroeg te berekenen. De browser spaart wijzigingen op en voert ze tegelijk uit. Leest JavaScript tussendoor een meting uit, dan moet de browser alles meteen verwerken. offsetWidth, getBoundingClientRect, scrollY en getComputedStyle al die reads triggeren dit.

Is CSS in de pagina zetten altijd de beste keuze?

Nee. Een los bestand komt bij een tweede bezoek uit de cache. Dat is winst. Inlinen versnelt het eerste bezoek en maakt herhaalbezoeken trager. Je kiest per site. Als bezoekers vooral via zoekresultaten binnenkomen, levert inlinen vaak winst op. Bij ons is dat gemeten. Het leverde winst op.

Moet je stoppen met content-visibility?

Nee. Door delen buiten beeld later te renderen verschijnt de eerste weergave echt sneller. De functie is niet het probleem. De audittool kan de kleur van een uitgesteld deel niet uitlezen. Schakel de functie alleen uit waar de tool een foute melding geeft.

Betekent 100 punten echt dat een site snel is?

Nee. Dit is een laboratoriummeting met een vast apparaat en een vast netwerk. Meet je dezelfde pagina direct opnieuw, dan schommelt de score tussen 98 en 100. Het echte beeld staat in de veldgegevens van Chrome. Die hebben tijd nodig om zich te vullen.

Waarom moet je alle pagina’s meten in plaats van een steekproef?

Bij ons kwam 13 van de 150 pagina's niet eens in de steekproef voor en 11 stonden in dezelfde taal. De homepage haalde 100, terwijl Arabische pagina's op 83 bleven steken. Over een pagina die je niet meet, kun je niets hard maken.

Bronnen

Gerelateerd werk