Anleitung: Wie man einen 100/100 Google Pagespeed erreicht

Was ist Google Pagespeed?

Google Pagespeed ist ein Onlinetool, welches die Performance deiner Seite anhand verschiedenster Parameter messen kann. Inzwischen wird zwischen mobiler und Desktopgeschwindigkeit unterschieden. Außerdem testet Pagespeed die Seite auch auf „Usability“. Neben der besseren User Experience wirkt sich ein guter Pagespeed auch positiv auf Ranking deiner Seite in der Google Suche aus, so dass es sich doppelt lohnt die Performance zu verbessern.

Vorwort

Von vornherein ist es wichtig zu sagen, dass eine Optimierung des Pagescores auf 100/100 nur ein Proof-of-Concept oder Experiment ist. Es ist zwar gut und wichtig, für einen hohen Pagescore zu optimieren, aber da die Regeln um auf 100/100 zu kommen so strikt und teilweise auch willkürlich sind, kann und sollte es nicht das Ziel sein, die volle Punktzahl zu erreichen. Manche Seiten werden sich schlicht nicht bis zur vollen Punktzahl optimieren lassen, ohne dass man die Funktionalität stark einschränken muss. Trotzdem kann man aus diesem Experiment einiges zum Thema Pagespeed-Optimierung lernen.

Eine durchschnittliche Seite, die nicht explizit geschwindigkeitsoptimiert ist, aber auch keine schwerwiegenden Fehler enthält, wird meiner Erfahrung nach, einen Pagescore zwischen 60 und 75 erreicht. Generell würde ich eine Punktzahl im mittleren 90er Bereich als Ziel für die meisten Projekte ansehen.

Ich habe bei mir zuerst nur die Homeseite meines Blogs auf 100/100 optimiert. Mein Blog läuft auf dem Kirby CMS – manche Optimierungen beziehen sich daher auch direkt auf dieses System. Daher ist es gut möglich, dass nicht alle Seiten mit den beschriebenen Techniken einen Pagescore von 100 erreichen werden.

Die meisten Punkte habe ich so aufgeteilt, wie Google auch die Fehlerquellen bei der Seitenanalyse benennt, allerdings werden sich manche Techniken inhaltlich überschneiden.

Die Ausgangslage und die bestehenden Fehlerquellen sind natürlich bei jeder Seite unterschiedlich, so dass es sein kann, dass für euch noch Probleme auftreten, auf die ich hier nicht eingehen kann, weil es sie bei mir bereits vor der Optimierung nicht gab.

Der Hoster

Wenn man generell Wert auf die Performance seiner Seite legt, sollte man natürlich als Grundvorraussetzung nicht bei dem günstigsten Sharedhoster seine Seite hosten. Für fortgeschrittene Nutzer empfehle ich Uberspace popup: yes. Sollte Google bei der Analyse bemerken, dass der Server langsam antwortet, wird dies mit „Die Antwortzeit des Servers reduzieren“ notiert.

Bilder optimieren

Um die Auswirkung des Bildgrößen auf den Pagescore besser einschätzen zu können, hab ich testweise mal drei viel zu große Fotos eingebunden und diese per CSS runterskaliert. Bereits mit nur drei Fotos ist mein Pagescore direkt auf 33/100 gefallen. Das zeigt, wie extrem die Bildoptimierung beim Pagespeed ins Gewicht fallen. Grundsätzlich gilt folgendes: Alle Bilder nur in der Größe hinterlegen, in der sie auch benutzt werden, also keine zu großen Bilder per CSS kleiner skalieren. In Kirby nutze ich hier für fast alle Bilder die eingebaute Thumbnail-Funktion. Zusätzlich kann man noch mit einem der vielen Image-Optimization-Tools alle Bilder verlustfrei ein wenig in der Größe reduzieren. Ein bisschen komplizierter wird es beim Thema Retina-Bilder. Hier muss man darauf achten, dass man über srcsets die Bilder in den richtigen Größen hinterlegt. Aber das ist noch mal ein komplettes Thema für sich, auf das ich noch einmal separat eingehen werde.

Browser-Caching nutzen

Das Browser-Caching ist an sich sehr einfach einzurichten, wenn der Server es unterstützt. Auf einem Apache-Server werden die Caching-Regeln in der .htaccess Datei definiert. Hier wird definiert, wie lang die heruntergeladenen Daten vom Browser gespeichert werden sollen. Das hilft zwar nicht beim ersten Besuch der Seite, aber bei Folgebesuchen. Für statische Dateien, die sich nicht häufig ändern, kann man die Zeit daher beispielsweise auf einen Monat hochsetzen. Ein Beispiel aus meiner .htaccess:

# Browser Caching
<IfModule mod_expires.c>
 ExpiresActive On
 ExpiresByType text/css "access plus 1 week"
 ExpiresByType text/javascript "access plus 1 month"
 ExpiresByType text/html "access plus 1 seconds"
 ExpiresByType application/javascript "access plus 1 month"
 ExpiresByType application/x-javascript "access plus 1 month"
 ExpiresByType application/xhtml-xml "access plus 600 seconds"
 ExpiresByType image/gif "access plus 1 week"
 ExpiresByType image/jpeg "access plus 1 week"
 ExpiresByType image/png "access plus 1 week"
 ExpiresByType image/x-icon "access plus 1 month"
 ExpiresByType image/svg+xml "access plus 1 week"
</IfModule>

Mit dieser Regel waren bei mir schon alle lokalen Caching-Probleme beseitigt. Wenn ihr statische Dateien in einem anderen Format benutzt, könnt ihr diese noch der Regel hinzufügen.

CSS reduzieren

CSS-Dateien zu minimieren hat sich inzwischen ja schon recht weitläufig durchgesetzt. Hierbei werden durch Preprozessoren oder entsprechende Tools die CSS-Dateien so verändert, das alle Kommentare, Zeilenumbrüche und Leerzeichen entfernt werden, um Speicherplatz zu sparen. Man arbeitet hier dann natürlich weiterhin mit einer nicht-reduzierten, dokumentierten Version zum Entwickeln und einer minimierten Live-Version. Zusätzlich sollte man einzelne CSS-Dateien zu einer Datei zusammenfassen, um die Anzahl der HTTP-Requests zu reduzieren. Dieser Punkt ist recht unkompliziert abzuhaken.

JavaScript reduzieren

Dieser Punkt ist auch relativ selbsterklärend. Alle JavaScript-Ressourcen sollten über einen Preprocessor oder ein entsprechendes Tool minimiert werden. Zusätzlich müssen die JavaScript-Dateien natürlich vor dem schließenden </body> Tag eingebunden werden und nicht im Header.

Render-blocking "above the fold" beseitigen

Hier wird es jetzt spannend und teilweise auch kompliziert. Mit „above the fold“ wird der Bereich der Seite bezeichnet, der gerendert wird, bevor man anfängt zu Scrollen. Also quasi alles, was in einem beliebigen Viewport beim Laden der Seite dargestellt wird. Das Problem ist jetzt, dass Ressourcen, die im <head> Bereich eingebunden werden, das Rendern dieses Bereiches blockieren, bis sie vollständig geladen wurden. Das sind zum Einen CSS-Dateien, die im <head> Bereich eingefügt sind, sowie JavaScript-Ressourcen, die man im <head> positionieren soll, da sie direkt benötigt werden. Dazu zählen zum Beispiel Webfonts, die z.B. von Google oder Typekit eingebunden werden. Für Webfonts gibt es die einfache Lösung, dass man statt der Standardeinbindung auf eine asynchrone Einbindung wechselt, die das Rendern nicht blockiert. Hier gibt es sowohl für Google Webfonts, als auch für Typekit eine Lösung. Leider hat man hier dann beim ersten Laden ein Blitzen des Textes, da der Text zunächst in der Standardschriftart geladen wird, und dann noch einmal tauscht sobald das asynchrone Laden abgeschlossen ist.
Komplizierter wird es bei der Verarbeitung der CSS Dateien. Google schlägt hier vor, dass man seine CSS-Datei in einen „kritischen“ Teil, der alles stylt, was im „above the fold“ Bereich sichtbar ist, und einen „Rest“ aufsplittet. Das „kritische“ CSS wird dann inline in einem <style> Tag im <head> eingefügt, so dass kein Request ausgelöst wird, sondern der Bereich direkt gerendert werden kann. Der Rest des CSS wird dann über eine externe Datei eingebunden, die allerdings nicht im <head> verlinkt ist, sondern vor dem schließenden <body>. Dies ist laut HTML5 Spezifikation auch erlaubt, auch wenn es fast nie angewendet wird.
Wie man sich vielleicht vorstellen kann, ist es eine mühsame und auch unflexible Arbeit, den kritischen CSS-Bereich zu extrahieren. Für manche Projekte mag das möglich sein, für viele aber nicht. Ich habe mich auf meiner Seite für folgenden Zwischenweg entschieden: Da meine gesamte CSS-Datei minimiert nur 13 KB groß ist, habe ich mich dazu entschieden, diese komplett inline einzubinden. Das mache ich in Kirby über das scssphp-Plugin, welches eine „critical“ Funktion zur Verfügung stellt. Hiermit wird dann aus einer hinterlegten SCSS-Datei eine minimierte Version automatisch inline eingebunden. Außerdem habe ich alle weiteren CSS-Ressourcen wie beschrieben im <body> eingebunden (aktuell ist das jedoch nur die CSS-Datei für meine Lightbox). Für größere und komplexere Projekte würde ich hier auf jeden Fall den minimalen Geschwindigkeitsverlust in Kauf nehmen und die CSS-Ressourcen klassisch einbinden.

Komprimierung aktivieren

Die Komprimierung macht folgendes: Daten, die vom Server zum Browser geschickt werden, werden automatisch von der Serversoftware komprimiert und vom Browser (in Echtzeit) wieder entpackt. Dadurch kann man summiert einige Kilobyte an übertragenen Daten sparen. So lang die Komprimierung vom Server unterstützt wird und konfigurierbar ist, hat das auch keine Nachteile. Die Komprimierung für statische Dateien auf Apache-Servern lässt sich auch über die .htaccess definieren:

AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/javascript text/javascript application/json

Die Komprimierung von dynamischen PHP-Skripten ist meist etwas komplizierter. Für die Einrichtung auf Uberspace habe ich hier bereits einen Artikel darüber geschrieben. Dort findet ihr auch einen Link zu einem Onlinetool mit dem man testen kann, ob die Komprimierung funktioniert.

Externe Ressourcen vermeiden oder: Die letzten Punkte sind die schwierigsten

Wenn ihr bis hierhin alle Fehler beseitigt habt, solltet ihr bereits im hohen 90-Punktebereich scoren. Um die letzten Pünktchen noch zu bekommen, wird es noch mal knifflig. Das Problem ist hier, dass man fast auf jeder modernen Seite Ressourcen von externen Servern einbindet. In meinem Fall sind das zum einen Google Analytics und zum Anderen YouTube-Video-Einbettungen. Weitere häufige Beispiele wären Google Adsense, Social Media Einbindungen wie Facebook, oder ähnliche. Da man bei diesen externen Ressourcen die Cachedauer nicht beeinflussen kann, und diese für Googles Empfinden meist zu kurz eingestellt ist (die Google Analytics JavaScript Bibliothek hat beispielsweise eine Cache Header von 2 Stunden), zieht Google Pagespeed hier Punkte ab. Google sagt auch selbst, dass sie zumindest im Fall von Google Analytics nichts daran ändern werden. Das ist natürlich irgendwo widersprüchlich, aber auch dahingehend konsequent, dass sie ihre eigenen Produkte nicht beim Speedtest unterschiedlich bewerten.

Google Analytics (analytics.js)

Wie bereits beschrieben, meckert Google bei der Cachezeit des Analyticsskriptes. Die einzige Möglichkeit diese Warnung zu beheben, ist die Datei selbst zu hosten, da man so dann die Cachedauer selbst definieren kann. Dazu lädt man sich einfach die eingebundene JavaScript-Datei und hostet sie selbst. Die Verlinkung passt man dann direkt im Script-Snippet an:

  <script>
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','/assets/js/analytics-local.js','ga');

    ga('create', 'XXXXXXXXXXXX', 'auto');
    ga('send', 'pageview');
  </script>

Das unangenehme ist hierbei natürlich, dass Google zwar selten, aber doch ein paar Mal im Jahr den Code der Datei update. Diese Updates kriegt man logischerweise in dieser Weise nicht eingespielt. Das einzige, was hier hilft, ist ein PHP-Script, welches über einen Cronjob automatisch regelmäßig läuft und die selbst-gehostete Datei mit der von Google abgleicht und diese dann ggf. aktualisiert. Um 100 Punkte zu erreichen ist diese Methode leider unumgänglich, allerdings würde ich auch auf diese Anpassung im echten Betrieb verzichten.

YouTube Einbindungen

Zuerst habe ich, wie ich bereits in diesem Blogpost beschrieben habe, die Einbindung von Youtube-Videos so angepasst, dass die Embed-Daten nur nach Klick auf das Video geladen werden. Dadurch spart man sich zum einen Daten und zum anderen werden keine JavaScript-Dateien mit schlechten Cachezeiten eingebunden. Mein Problem war dabei dann aber, dass ich zunächst auf die von YouTube bereitgestellten Thumbnails zurückgegriffen habe, die auf einem YouTube CDN liegen. Auch für diese Bilder ist eine zu kurze Cachezeit eingestellt, so dass ich das Script so anpassen musste, dass die Thumbnails vom YouTube CDN herunter– und auf meinen Server hochgeladen werden. Damit kann ich dann komplett externe Ressourcen umgehen (natürlich nur, bis jemand ein Video schaut, aber darum geht es ja auch nicht).

Für Facebook oder Google+ Sharebuttons gibt es ein ähnliches Verfahren, was die externen Ressourcen erst lädt, nachdem man auf einen der Sharebuttons geklickt hat.

Fazit

Mit diesen Anpassungen habe ich einen Pagespeed von 100/100 für Mobil & Desktop erreicht. Außerdem auch 100 Punkte für die Nutzererfahrung auf mobilen Geräten, was aber nicht direkt etwas mit der Performance zu tun hat, sondern nur mit gutem responsiven Design.