WordPress auf Google Pagespeed 98/100 tunen: Tipps, Tricks & Erfahrungen (2017)

Langsame Seiten im Internet sind unschön, erhöhen die Absprungraten und sorgen für Frust. Deshalb war es mir ein großes anliegen meine Firmenseite (basierend auf WordPress) auf einen Google Score von 95/100 zu optimieren. Welche Möglichkeiten und Fallstricke WordPress dabei auftauchten, zeigt der folgende Artikel.

Das Webserver Environment

  • Hetzner V-Server
  • Debian
  • Apache/2.4.25 (Debian)
  • nginx/1.11.13
  • Varnish VCL 4
  • HTTP/2 und SSL Support
  • WordPress mit u.a. folgenden Plugins:
    • Woocommerce
    • WP Super Cache
    • Wordfence
    • BWP-Minify

Die Ausgangssituation: 60/100

Ein paar Optimierungen werden von meinem Server-Environment bereits abgedeckt:

  • Vermeidung von Redirects: Varnish & nginx redirect auf SSL + www zu https://www.solid-creation.com
  • Kompression Aktivieren: Deflate und gzip in der .htaccess wurde aktiviert
  • Varnish cached CSS & Javascripts

Jedoch fehlen noch die Punkte

  1. HTML reduzieren
  2. Bilder optimieren
  3. CSS reduzieren
  4. Sichtbare Inhalte priorisieren
  5. JavaScript- und CSS-Ressourcen, die das Rendering blockieren, in Inhalten “above the fold” (ohne Scrollen sichtbar) beseitigen

Wieso nicht 100/100 sondern 95/100?

Es ist mit einem Externen Script nicht möglich auf 100 Punkte zu kommen . Über das Browser Caching von Externen Scripts hat man keinen Einfluss.

Es gibt die Möglichkeiten das Google Analytics Script lokal abzulegen oder ein Google Light Script (3rd Party) zu benutzen. Allerdings kann darunter das Tracking leiden oder gar zerstört werden, weshalb ich davon abrate. Dennoch ist das keine Einbuße. Die Scripts werden asynchron geladen (wie wir später sehen werden) und sind auf vielen anderen Websites vertreten. Dadurch sind sie bereits im Browsercache gespeichert und werden ohnehin von dort aus schnell geladen. dieser Punkt ist deshalb zu vernachlässigen.

HTML reduzieren

Ich bin sehr begeistert von der simplen Lösung von Tim Eckel namens Minify HTML. Einfach installieren und Optionen wählen:

Bilder optimieren

Für die Bildoptimierung habe ich mich dazu entschieden den aktuellen Bestand offline verfügbar zu machen und per grunt imagemin zu generieren. Die Option dazu ist wie folgt eingestellt:

.... ,
imagemin: {                         
    uploads: {
        options: {
            progressive: true
        },
        files: [{
            expand: true,
            cwd: 'wp-content/uploads/',
            src: [
                '**/*.jpg',
                '**/*.png'
            ],
            dest: 'wp-content/uploads/'
        }]
    }
}, ....

Dank einer eingebauten Routine werden Bilder, die bereits komprimiert sind nicht erneut komprimiert. Wichtig ist jedoch, dass vor dem Einsatz des Tools ein Backup der Bilder gemacht wird um ggf. zurück switchen zu können.

JavaScript- und CSS-Ressourcen, die das Rendering blockieren, in Inhalten “above the fold” (ohne Scrollen sichtbar) beseitigen & CSS verkleinern

Das wohl komplexeste Problem, das es zu beheben gab. Was hierfür benötigt wird, ist

Vorbereitung von Template-Assets bevor WordPress sie verarbeitet

Da ich mein Template weiterentwickeln möchte und dabei auf Wartbarkeit wert lege, nutze ich den Grunt Task Runner. Eine wunderbare Art modular zu arbeiten und Assets zu verarbeiten. Ziel ist es nur ein einzelnes Javascript und Stylesheet zu generieren.

Ich bevorzuge jQuery selbst im Template zu integrieren, ebenso wie jQuery UI. Außerdem wird es Libraries wie waypoints enthalten und weitere Scripts, die zum Aufbau der Seite notwendig sind (Menüs, Parallax Scrolling,..).

WordPress – Möglichkeiten und Tricks

In erster Linie kollidierten meine Ansätze aus dem Frontend-Alltag mit dem WordPress Kodex. Durch den Nutzen von Hooks und verschiedenen Plugins wurde die Handhabung im Backend schnell unübersichtlich und schlecht nachvollziehbar. Es gibt für mich zudem kein befriedigendes Plugin, das out of the box CSS & Javascript-Dateien kombiniert, komprimiert uns korrekt asynchron ausliefert.

Mithilfe des Plugins bwp-minify konnte ich zumindest Dateien kombinieren und dem Footer oder Header zuordnen. Jedoch blockierten einige Javascripts immer noch das Rendering. Um das zu umgehen schrieb ich die class-bwp-minify.php um um Javascripts per defer auszugeben und CSS mithilfe von load CSS einzufügen.

wp-content\plugins\bwp-minify\includes\class-bwp-minify.php

Optimierung für Asynchrones Javascript:

case 'script':
   $return  = "<script type='text/javascript' defer='defer' src='"
      . esc_url($string)
      . "'></script>\r\n";
   break;

Optimierung für loadCSS:

case 'style':
   $return = "<link rel='preload' as='style'  onload='this.rel=\"stylesheet\"'  id='"
      . esc_attr($group_handle) . "-group-css' href='"
      . esc_url($string) . "' media='"
      . esc_attr($media) . "' />\r\n";

FYI: Diese Praxis kann Nebeneffekte haben: Beim Updaten des Plugins werden die manuellen Änderungen überschrieben und gehen verloren. Deshalb sollte es kein Update des Plugins geben.

Javascripts korrekt einbinden

Dank der Änderung im BWP-Plugin sollte die Lösung bereits funktionieren. Wenn nicht. sind noch nicht alle Scripts korrekt registriert. In meinem Fall fehlte noch der defer-Tag. Aber wieso war das so? Das Geheimnis ist hartnäckig aber doch simpel:

statt

wp_register_script();

wird

wp_enqueue_script();

benutzt. Dadurch wird das Script korrekt in die Hook ein gehangen und durch das Plugin geschliffen.

Damit der defer Tag hinzugefügt wird, fügen wir ins der functions.php des Templates eine Funktion hinzu, die alle Links analysiert. Sofern ein Javascript erkannt wird, soll das defer-Attribut hinzugefügt werden:

if ( ! function_exists( 'add_defer_to_scripts' ) )
{
    function add_defer_to_scripts( $url )
    {
        // analyse url structure for extentions, just modify .js files
        if(strpos($url,".js")){
            return "$url' defer='defer";
        }
        else {
            return $url;
        }
    }
    add_filter( 'clean_url', 'add_defer_to_scripts', 11, 1 );
}

Stylesheets für loadCSS korrekt einbinden

Zuerst wird das loadCSS Script in den <head> Tag eingefügt per inline Script-Tag. Der Stylesheet Tag muss nun wie folgt geändert werden:

<link rel="preload" href="//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" as="style" onload="this.rel='stylesheet'">

Beim onload() Event wird das Stylesheet aktiviert und geladen. Dadurch wird der Browser nicht aufgehalten und kann schon rendern ohne das Stylesheet während des GET Requests zu laden. Dies verursacht ein Flackern während des Ladevorgangs. Deshalb muss unbedingt ein CriticalCSS Path hinzugefügt werden. Wie man diesen generiert wird in diesem Artikel von Smashing Magazine genauer beschrieben.

Varnish als Performance-Ergänzung möglich und sinnvoll

Es kam mir sehr kompliziert vor allein diesen Vorgang zu definieren und ein solides Konstrukt aufzubauen. Es sind Javascript Abhängigkeiten entstanden im Frontend & Backend, die mit viel Geduld, Trial & Error und Hoffnung in die Frontend-Technologien gefixt werden konnten. Jedoch besteht die große Chance, dass neue Plugins neue Probleme verursachen werden. Je nach Manpower und Motivation des Plugin-Autors kann sich der ein oder andere Fehler einschleichen ohne böse Absicht.

Neben der Vorbereitung von Assets war auch die Challenge eine besondere, den Varnish Cache ordentlich zu konfigurieren. Denn:

Cookies und Varnish beißt sich

Beim Einsatz des Caching Layers ist es sinnvoll alle Assets wie CSS und Javascript zu cachen. Der GET Request kann ebenfalls gecached werden. Jedoch gilt das nur für Requests, die keine Cookies oder eine Session haben. Sofern man WordPress-Plugins wie Wordfence Security nutzt, wird auf allen Seiten ein wfvt_ Cookie gesetzt. Dadurch reagiert Varnish mit einem Miss. Als Lösung empfehle ich das Plugin zu deaktivieren, wenn man genügend Sicherheitsvorkehrungen am Server hat oder nur auf Asset-Caching wert zu legen.

Woocommerce und Varnish Caching ist ohne Session möglich

Sobald Sessions zum Einsatz kommen, wird es heikel. Denn PHPSession verhindern ebenfalls ein Caching. Das kommt daher, weil jeder User eine unabhängige User Session bekommt. Sonst würden verschiedene User sich den gleichen Warenkorb teilen. In der Praxis bedeutet das, dass es nicht mehr möglich ist eine Seite zu cachen, sobald man ein mal einen Warenkorb in Woocommerce befüllt hat. Es gibt Strategien mit Key Value Caches wie Radis zu arbeiten. Doch ist diese Lösung für mein WordPress-Projekt überdimensioniert. Möchte man einen souveränen Webshop betreiben, empfehlen sich andere Shopsysteme wie Magento, shopware o.ä. Diese sind für bessere Caching Strategien ausgelegt.

Résumé

Es war ein sehr spannendes Projekt, wenn auch sehr mühsam. Diese Anleitung kann jedoch für alle weiteren Projekte übernommen werden, ausgenommen der Varnish-Konfiguration. Diese wird für jedes Projekt separat angepasst aufgrund verschiedener Anforderungen.

Varnish ist nicht unbedingt notwendig, bringt jedoch den Gewinn von über einer halben Sekunde. Je nach Assetumfang kann es auch noch mehr an Kontingent freisetzen.

Aufgrund der Aufbereitung und Verschmelzung von CSS/Javascripts via php kommt es oft zu Ladeproblemen nach dem Cache löschen. Mit mehrfachem refresh wird letztendlich bwp-minify damit fertig alles zu generieren und in Varnish zu übergeben. Jedoch muss ich ein manuelles Cache Warming durchführen, damit die Seite wie gewünscht läuft.

Résumé ist also: WordPress kann schnell gemacht werden, ist jedoch mit Aufwand und einigen Sideeffects verbunden. Für professionelle Unterstützung stehe ich Ihnen gern zur Seite. Ich empfehle das Performance Boost Paket im solid-creation Shop zu buchen. Ich Analysiere die Performance-Probleme und behebe Sie mit Ihnen gemeinsam im Rahmen des gebuchten Umfangs.