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.
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
getComputedStylecalls 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); }); }
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:
De lettertypen die een Turkse pagina downloadt. Geen 6 bestanden en 2 externe herkomsten, maar 3 bestanden vanaf één herkomst.
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}
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
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.
Layout shift op Arabische pagina’s (CLS)
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.