Google pleasen; CSS optimaal serveren

  • ± 7 minuten

Heb je je website al eens door PageSpeed Insights gehaald? Naast JavaScript kan toepassing van CSS de grootste impact hebben. Meermaals kreeg ik de vraag hoe wij dit tackelen voor onze opdrachtgevers.

Al eens eerder deed ik een onderzoek naar verschillende methoden en de impact op de "Defer unused CSS" melding van Lighthouse, als ook de First Meaningful Paint metric. De tactiek die het best werkbaar was, wordt standaard uitgerold binnen websites van onze opdrachtgevers. Steeds vaker krijg ik de vraag: hoe doe je dit nu exact? Hierbij stapsgewijs het antwoord:

Minimize CSS

Zorg dat de benodigde CSS geminimaliseerd wordt. Heb je een kleine website? Dan kun je het beste alle CSS samenvoegen alvorens te gaan minimizen.

Minimizen kan door gebruik te maken van developer toolsets als Gulp of Grunt, als ook automatisch door het CMS. De stap van CSS minimaliseren, wordt voor onze klanten door het CMS geautomatiseerd. Zo heeft de klant noch onze developers er omkijken naar.

Disclaimer: Voor websites of webshops met meer omvangrijkere stylesheets, kan het verstandig zijn een mechanisme in te bouwen, waarbij specifieke styling-onderdelen pas ingeladen worden op het moment dat ze gebruikt gaan worden.

Lazy-load CSS

CSS dient door de browser eerst gedownload als ook geparsed te worden, alvorens de browser kan beginnen met het vullen van je beeldscherm. Op het moment dat de browser een stylesheet-verwijzing in het HTML document tegen komt, stopt het verdere uitvoer van de HTML. Dezelfde vlieger gaat op voor script-elementen. Oftewel, stylesheets en scripts zijn "Render blocking". Zie onderstaande afbeelding uit de performance fundamentals guidevan Google hoe zich dit visueel vertaalt.

Met deze vorm van 'network latency' gaan dus kostbare seconden gemoeid, waardoor bezoekers een langzamere website ervaren. Deze vertraging moeten we wegsnijden om tot betere performance en conversie te komen. Het wegsnijden ofwel lazy-loaden van CSS kan via de volgende opzet van het link-element:

[pre]<link href="style.css" rel=stylesheet media=none onload="this.media='all';this.onload=null">[/pre]

Zodra de browser klaar is met taken die zich op het kritieke pad bevinden, is JavaScript aan de beurt. Daarop zal bovenstaande regel middels het onload-attribuut dynamisch gewijzigd worden naar een link-element waarbij de stylesheet gedownload, CSS geparsed en styling toegepast wordt.

Lazyload, preload en browser support

Er zijn alternatieven, waaronder:

  • lazyload attribuut, welke nog onvoldoende support vanuit browser kent;
  • in plaats van "stylesheet" als rel-waarde, kan er preload worden gebruikt. Op die manier zullen browsers een bestand alvast downloaden, zonder dat het de kritieke pad blokkeert (het document wordt namelijk nog niet 'uitgerold' door de browser). Echter, wederom door onvoldoende support, is hiervoor aanvullende JavaScript benodigd, wat ook weer zijn (negatieve) invloed heeft op de performance.
    Bij websites met aanzienlijke CSS, zal deze constructie pas in verhouding staan: uit eigen tests bleek dat preloaden bij minder grote websites ook op langzame internetverbindingen geen (positieve) impact had.

Noscript variant

Omdat deze variant op JavaScript rust, is het verstandig een reeds werkend link-element op te nemen in een noscript-element. Browsers waarin JavaScript om welke reden dan ook is uitgeschakeld, krijgen via deze opzet alsnog styling toegepast.

[pre]<noscript><link rel=stylesheet href="style.css"></noscript>[/pre]

Dit proces valt in een CMS te automatiseren, indien de flexibiliteit van een CMS zich hier voor leent.

Critical Path CSS

Gevolg van CSS lazy-loaden is dat de bezoeker eerst een onopgemaakte (tekst)pagina te zien krijgt, onwenselijk natuurlijk! Om dit te voorkomen, kun je de CSS die benodigd is voor het initieel voor de bezoeker zichtbare gedeelte (het "above the fold" gedeelte) direct in het HTML document plaatsen (oftewel, in een style-element in de head van je HTML document).

Voor dit stukje CSS is geen download benodigd, en doordat je middels lazy-loading van CSS voorkomt dat de browser eerst op aanvullende CSS moet wachten, begint de browser direct met vormgeven van je webpagina. Dit resulteert als gevolg bovendien in een meer optimale First Meaningful Paint.

Disclaimer: dit werkt enkel optimaal indien ook andere bronnen van de kritieke pad (Critical Render Path) zijn gehaald, zoals JavaScript.

Critical CSS bepalen

Bepalen welke CSS je direct in de HTML document moet plaatsen, kan op verschillende manieren:

  1. Via een developer toolbar in je favoriete browser per element nagaan welke CSS er mee gemoeid gaat;
  2. Via een online tool, zelf gebruik ik daarvoor wel eens https://jonassebastianohlsson.com/criticalpathcssgenerator/;
  3. Via een developer tooling, zoals Gulp of Grunt.

Voorkom in ieder geval dat je te veel CSS in de head opneemt. Bijvoorbeeld, styling voor specifieke toestanden als hover, active of focus zijn in de eerste seconden nog niet benodigd. Bedenk ook of een iconenset als FontAwesome thuishoort op de kritieke pad. Probeer het antwoord "nee" te laten zijn, zodat ook een FontAwesome via lazy-loading ingeladen kan worden.

Arbeidsintensieve CSS

Heeft je website (veel) verschillen templates, waardoor het bepalen van de kritieke CSS een arbeidsintensieve klus is? Dan kun je besluiten om alleen de header en bijvoorbeeld een hero-afbeelding -waarvan styling op elke pagina hetzelfde zal zijn- via kritieke CSS direct te laten stylen, om vervolgens de content in eerste instantie onzichtbaar te maken.

Google's AMP gebruikt een vergelijkbare techniek, waarbij de content met een animatie-effect ineens zichtbaar wordt. Dit is zo subtiel, dat je dit als gebruiker niet verneemt. Deze techniek is ook in onze demo omtrent Defer Unused CSS waar te nemen.

Dit doen ze door de volgende CSS direct op te nemen in het HTML document:

[pre]<style type="text/css">body { opacity: 0; transition: opacity 0.1s ease-in; }</style>[/pre]

Waar body staat, kunnen natuurlijk specifieke elementen waarin de hoofd-content zich bevindt, gebruikt worden. Vervolgens zet je de opacity-waarde van de betreffende HTML element(en) in de stylesheet weer op 1, zodat deze elementen zichtbaar worden zodra de hoofd-stylesheet geladen is.

De laatste procent-punt

Ondanks bovenstaande toepassingen kan de melding omtrent "Defer unused CSS" boven water blijven komen. De Lighthouse analyse zou idealiter zien, dat alleen CSS wordt geparsed dat ook daadwerkelijk gebruikt gaat worden op de betreffende pagina. Dit komt overeen met de gedachtegoed van AMP, wat overigens één van onderstaande kanttekeningen heeft (HTML documenten nemen in grootte toe), wat de (vermoedelijke) reden is dat AMP een beperking in te gebruiken kilobytes kent.

Zoals aangegeven onder subkop "Lazyload, preload en browser support", levert dit een groter verschil op bij websites waar meer styling bij komt kijken (zoals webshops).

Kanttekening bij lazy loading van CSS

Iedere Web Performance Optimizer ziet direct in: al die CSS in het HTML-document heeft als gevolg dat het HTML document in kilo-bytes toeneemt. Terwijl je de website probeert sneller te maken, wordt het op andere vlak langzamer, want:

  • zonder server side caching zal je TTFB metric er op achteruit gaan;
  • je Content-download zal hoger worden als gevolg van grotere HTML documenten c.q. meer kilobytes;
  • het voordeel van caching door de browser (client side caching) wordt hierbij niet benut, doordat CSS herhaaldelijk in het HTML document staat opgenomen;
  • je kunt al snel CSS twee maal gedeclareerd hebben, wanneer je de kritieke CSS niet uit je stylesheets verwijdert, wat ook resulteert in onnodige te downloaden kilobytes door je bezoekers.

Defer unused CSS in Wordpress niet optimaal

Bovenstaand is waar Wordpress last van heeft. Wordpress kent plugin(s) om de CSS uit stylesheets samen te voegen en direct in het HTML document te plaatsen. Echter:

  • dat gebeurt op elke pagina waardoor pagina's over de gehele website in grootte en download-tijd toeneemt;
  • soms wordt de gehele CSS toegepast, in plaats van enkel CSS dat verantwoordelijk is voor "Above the fold", met verdere onnodige toename in grootte en download-tijd als gevolg;
  • het voordeel van browser / client side caching wordt niet benut;
  • zonder caching neemt de server side uitvoertijd (TTFB) toe;
  • dit gebeurt binnen Wordpress soms onvolledig, doordat een plugin niet altijd in staat is de stylesheets van andere plugins te benaderen;
  • veelal wordt ook CSS voor fonts en font-verwijzingen opgenomen in de Critical CSS, waardoor het nieuwe render-blocking requests opwerpt.

In Wordpress wordt de kritieke CSS dan ook niet optimaal getackeld. Sterker: terwijl gepoogd wordt met een plugin de performance te verbeteren, wordt de performance juist op andere vlakken minder optimaal.

Je pagespeed score zal wellicht omhoog gaan, maar de werkelijk ervaren snelheid kan daarmee in verval raken. Hier schreef ik reeds eerder over in het artikel: "8 redenen waarom je SEO / performance verkeerd doet".

Kers op de taart

Omdat bovenstaande kanttekening ons beroepsmatig tegen de borst stuitte, hebben we het CMS voorzien van een mechanisme dat het lazy-loading van CSS, enkel bij de eerste pagehit binnen een sessie toepast. De zogenaamde kers op de taart om de "Defer unused CSS" melding te tackelen.

Het voordeel? Alle kanttekeningen uit bovenstaande worden weggenomen;

  • Een webpagina kan sneller door de browser worden ingekleurd;
  • Het voordeel van client side caching blijft intact;
  • Het aantal kilobytes is in verhouding enkel bij de eerste pagehit groter;
  • In combinatie met server side caching, blijft ook de TTFB optimaal.

Om deze mechanisme toe te passen, wordt er gecontroleerd of men in het bezit is van een specifieke session. Indien dit (nog) niet het geval is, zal het CMS automatisch Critical Path CSS toepassen (of indien al eens gegenereerd, de juiste cache voorschotelen).

Het nadeel? Behalve dat een CMS voorzien moet worden van verschillende mechanismen, zijn er voor bezoekers juist geen nadelen. De arbeidsintensiviteit om de verschillende mechanismen door te voeren in software, zal volledig afhangen van de gebruikte software.

Vragen over deze techniek? Hanteer je als webbureau een eigen CMS waarin je deze mechanismen graag doorgevoerd wil hebben? Neem gerust contact met me op!