Google PageSpeed Insights 100/100 für TYPO3

András Ottó von der Marit AG hat vor kurzem ein schönes und informatives Dokument [1] zum Thema Google PageSpeed Insights [2] mit dem passenden Namen "PageSpeed Insights 100/100 guide" veröffentlicht. Da ich selbst vor kurzem meine Seite neu gelauncht habe, war dass der passende Zeitpunkt, um das ganze etwas tiefer zu testen, und nach Möglichkeit die besten Werte zu erreichen.

Hinweis: Dieser Artikel ist veraltet. Eine neue Version des Artikels ist hier zu finden: PageSpeed 100/100. TYPO3 schneller machen, richtig viel schneller!

Die Auswertung teilt sich dabei in 3 Kategorien auf:

  • Mobil Performance
  • Mobil Nutzererfahrung
  • Desktop Performance

Bei der Nutzererfahrung, die für die mobile Ansicht der Webseite gilt, prüft das Tool unter anderem, ob die Webseite auch mit dem Smartphone betrachtet und bedient werden kann.

Im Bereich der Performance werden viele technische Aspekte geprüft. Unter anderem die Antwortzeit des Servers, ob Browser-Caching genutzt wird, wie das CSS sowie das JavaScript ausgeliefert wird und, ein ganz "spezieller" Punkt: ob die sichtbaren Inhalte priorisiert werden. Speziell deswegen, weil dieser Punkt wohl den meisten Kopfzerbrechen bereiten dürfte.

Wer nun damit beginnt, sein Projekt zu optimieren, wird schnell feststellen, dass mit dem erreichen der 90 oder 92 Punkte meistens Ende der Fahnenstange ist. Oftmals bleiben dann nur noch 2 oder 3 Punkte übrig, die gemacht werden müssen. Das eine ist das korrekte Browsercaching, welches noch leicht zu beheben ist, und das andere die Priorisierung der Inhalte "above the fold". In der Theorie ist das ganze recht simpel zu lösen: Die Elemente die sich im Sichtbereich befinden, sollen ohne zusätzlichen Request an den Server dargestellt werden können. Das bedeutet, alle CSS Klassen die für die Darstellung benötigt werden, müssen bereits mit dem HTML Dokument Inline ausgeliefert werden.

Da diese Aufgabe sich von Projekt zu Projekt anders gestaltet, ist es schwierig, ein einheitliches Konzept zur Umsetzung zu finden. Das Problematische an der Umsetzung ist, dass es sich dabei nicht nur um CSS, sondern auch um JavaScript handeln kann.

Aber ganz von vorne. Du solltest entsprechend dem Dokument auf Slideshare vorgehen. In meinem Artikel gehe ich auf einige Punkte ein und stelle den Code hier in kopierbarer Form zu Verfügung. Abtippen macht nämlich keinen Spass :-)

Alles beginnt mit der Komprimierung, in Google PageSpeed mit dem Fehler "Komprimierung aktivieren" benannt. Hierzu reicht es aus, in der .htaccess im Document Root die Komprimierung zu aktivieren. Damit das ganze läuft, müssen folgende Module geladen sein:

  • mod_deflate
  • mod_setenvif
  • mod_headers

In der Regel sind diese Module auf dem Webspace verfügbar. Wenn nicht, solltest Du mit Deinem Hoster über die Bereitstellung der Module sprechen.

Die Platzierung des Codes spielt in der .htaccess keine sonderlich große Rolle. Ich habe es am Ende eingefügt.

 

<IfModule mod_deflate.c>
 <IfModule mod_setenvif.c>
 <IfModule mod_headers.c>
 SetEnvIfNoCase ^(Accept-Encoding|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~]{4,13}$ HAVE_Accept-Encoding
 RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
 </IfModule>
</IfModule>
AddOutputFilterByType DEFLATE application/atom+xml \
 application/javascript \
 application/json \
 application/rss+xml \
 application/vnd.ms-fontobject \
 application/x-font-ttf \
 applicationx-web-app-manifest+json \
 application/xhtml+xml \
 application/xml \
 font/opentype \
 image/svg+xml \
 image/x-icon \
 text/css \
 text/html \
 text/plain \
 text/x-component \
 text/xml
</IfModule>

 

Als nächstes wird festgelegt, was der Browser cachen darf, und vor allem, wie lange er das darf. Diese Lösung gilt für das Problem "Browser-Caching nutzen" in Google PageSpeed. Hierfür wird das Apache Module mod_expires benötigt. Auch dass sollte in der Regel auf dem Server installiert sein. Der folgende Code kann wieder an das Ende der .htaccess Datei.

 

<IfModule mod_expires.c>
 ExpiresActive On
 ExpiresDefault "access plus 0 month"

 ExpiresByType text/css "access plus 1 month"

 ExpiresByType application/json "access plus 0 seconds"
 ExpiresByType application/xml "access plus 0 seconds"
 ExpiresByType text/xml "access plus 0 seconds"

 ExpiresByType image/x-icon "access plus 1 week"

 ExpiresByType text/x-component "access plus 1 month"

 ExpiresByType text/html "access plus 0 seconds"

 ExpiresByType application/javascript "access plus 1 month"

 ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
 ExpiresByType text/cache-manifest "access plus 0 seconds"

 ExpiresByType audio/ogg "access plus 1 month"
 ExpiresByType image/gif "access plus 1 month"
 ExpiresByType image/jpeg "access plus 1 month"
 ExpiresByType image/png "access plus 1 month"
 ExpiresByType video/mp4 "access plus 1 month"
 ExpiresByType video/ogg "access plus 1 month"
 ExpiresByType video/webm "access plus 1 month"

 ExpiresByType application/atom+xml "access plus 1 hour"
 ExpiresByType application/rss+xml "access plus 1 hour"

 ExpiresByType application/font-woff "access plus 1 year"
 ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
 ExpiresByType application/x-font-ttf "access plus 1 month"
 ExpiresByType font/opentype "access plus 1 month"
 ExpiresByType image/svg+xml "access plus 1 month"
</IfModule>

 

Bereits nach diesem kleineren Eingriff sollte Google PageSpeed über 90 sein. Eventuell hängt die Punktezahl auf dem "Mobile"-Tap noch hinterher.

Ab Slide 20 geht András auf das Problem mit "JavaScript" reduzieren ein. Bisher war ich auch der Meinung, dass dieser Code ausreichen sollte:

 

config {
 concatenateCss = 1
 concatenateJs = 1
 compressCss = 1
 compressJs = 1
}

 

Leider weit gefehlt. Und ab jetzt wird es tricky. Die Idee mit grunt ist hervorragend und ich würde jedem empfehlen, es zu nutzen. Auf dem Server sollte Node.js und der Package Manager npm installiert sein. Du kannst das prüfen, indem Du auf der Konsole 

 

rutschmann@rutschmann.biz:~$ npm --version
1.4.21
rutschmann@rutschmann.biz:~$

 

eingibst. Sofern eine Version angezeigt wird, kannst Du gleich prüfen, ob grunt installiert ist:

 

rutschmann@rutschmann.biz:~$ grunt --version
grunt-cli v0.1.13
rutschmann@rutschmann.biz:~$ 

 

Schaut gut aus. Wenn dass nicht gegeben ist, oder Du nicht auf der Konsole weiter kommst, sollte Du die Files manuell (zum Beispiel mit jscompress.com und csscompressor.com) erstellen. Sofern Du mit phpStorm arbeitest, kannst Du auch den YUI Compressor nutzen. Ob der aber ähnlich gute Resultate liefert, kann ich an dieser Stelle nicht bewerten.

Um mit grunt die Dateien aufbereiten zu können, werden die folgenden Packages benötigt:

  • grunt-contrib-cssmin
  • grunt-contrib-concat
  • grunt-contrib-uglify

Im Document Root werden für grunt 2 Files benötigt. Einmal die "package.json" und einmal die "Gruntfile.js". In meinem Projekt schaut die package.json folgendermaßen aus:

 

{
 "name": "rutschmann.biz",
 "version": "0.1.0",
 "devDependencies": {
 "grunt": "~0.4.5",
 "grunt-contrib-cssmin": "~0.10.0",
 "grunt-contrib-concat": "~0.5.1",
 "grunt-contrib-uglify": "~0.5.0"
 }
}

 

Und hier das Gruntfile.js:

 

module.exports = function(grunt) {
    grunt.initConfig({
        concat: {
            modules: {
                files: {
                    'fileadmin/bsdist/theme/js/main.js': [
                        'fileadmin/bsdist/theme/js/jquery-1.11.1.min.js',
                        'fileadmin/bsdist/theme/js/bootstrap.js',
                        'fileadmin/bsdist/theme/js/jquery.particleground.js',
                        'fileadmin/bsdist/theme/js/particle.js',
                        'typo3conf/ext/bootstrap_grids/Resources/Public/Flexslider2/jquery.flexslider-min.js',
                        'fileadmin/bsdist/theme/js/jquery.easytabs.min.js',
                        'fileadmin/bsdist/theme/js/easytabs-settings.js',
                        'fileadmin/bsdist/theme/js/testimonialcarousel.js',
                        'fileadmin/bsdist/theme/js/responsiveCarousel.js',
                        'fileadmin/bsdist/theme/js/jquery.appear.js',
                        'fileadmin/bsdist/lib/jquery-prettyPhoto/js/jquery.prettyPhoto.js',
                        'fileadmin/bsdist/theme/js/jquery.easypiechart.js',
                        'fileadmin/files/typo3-layerslider/layerslider/js/greensock.js',
                        'fileadmin/files/typo3-layerslider/layerslider/js/layerslider.transitions.js',
                        'fileadmin/bsdist/theme/js/settings.js',
                        'fileadmin/bsdist/theme/js/flexslider-settings.js',
                        'fileadmin/files/typo3-layerslider/layerslider/js/layerslider.kreaturamedia.jquery.js',
                        'fileadmin/files/typo3-layerslider/layerslider/js/start.js',
                    ],
                    'fileadmin/bsdist/theme/css/main.css': [
                        'fileadmin/bsdist/theme/css/bootstrap.css',
                        'typo3conf/ext/bootstrap_grids/Resources/Public/Flexslider2/flexslider.css',
                        'fileadmin/bsdist/theme/font-awesome/css/font-awesome.css',
                        'fileadmin/bsdist/theme/css/animate.min.css',
                        'fileadmin/bsdist/lib/jquery-prettyPhoto/css/prettyPhoto.min.css',
                        'fileadmin/files/typo3-layerslider/layerslider/css/layerslider.css',
                        'typo3conf/ext/rflipbook/Resources/Public/css/flipbook.style.css',
                        'fileadmin/bsdist/theme/css/style.css'
                    ],
                }
            }
        },
        uglify: {
            main: {
                files: {
                    'fileadmin/bsdist/theme/js/main.min.js': ['fileadmin/bsdist/theme/js/main.js'],
                }
            }
        },
        cssmin: {
            options: {
                keepSpecialComments: 0
            },
            main: {
                files: {
                    'fileadmin/bsdist/theme/css/main.min.css': ['fileadmin/bsdist/theme/css/main.css']
                }
            }
        },
    });
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.registerTask('default', ['concat', 'uglify', 'cssmin']);
};

 


  

 

Im ersten schritt werden alle JS Dateien zu einer Datei zusammengeführt. Hier kann oft was schiefgehen. Wichtig ist die Reihenfolge der Dateien. Wie im Slide vorgeschlagen, sollte am besten über den TypoScript Object Browser geschaut werden, auf welchen Seiten welche JS Files zum Einsatz kommen. Natürlich ist es auch möglich, mehrere JS und CSS Dateien zu erzeugen. Das macht zum Beispiel dann Sinn, wenn auf der Startseite mehr JS und CSS eingebunden wird, als auf den restlichen Seiten. Mittels einer TypoScript Condition kann dann nachher explizit gesteuert werden, wo welches zusammengefügte File eingebunden werden soll.

Nachdem die Dateien zusammengeführt werden, wird das JS mittels uglify komprimiert. Das CSS wird mittels cssmin komprimiert. Wichtig hierbei ist, dass in den Options "keepSpecialComments: 0" angegeben wird. Google PageSpeed schlägt bei Kommentaren in Dateien nämlich eine weitere Komprimierung vor.

Auf der Konsole kann grunt dann gestartet werden. Im besten Fall treten keine Fehler auf, der Report sollte dann so ähnlich aussehen:

 

rutschmann@rutschmann.biz:~/public_html$ grunt
Running "concat:modules" (concat) task
File fileadmin/bsdist/theme/js/main.js created.
 File fileadmin/bsdist/theme/css/main.css created.

 Running "uglify:main" (uglify) task

Running "cssmin:main" (cssmin) task
File fileadmin/bsdist/theme/css/main.min.css created: 379.62 kB ? 319.65 kB

Done, without errors.
rutschmann@rutschmann.biz:~/public_html$ 

 

Die zusammengefügten und komprimierten Dateien liegen nun auf dem Server im Zielverzeichnis. Sofern Du keine Fehler gemacht hast und alle nötigen Scripts eingebunden hast, kannst Du nun die neuen Dateien in Dein Projekt einbinden. Dazu muss das TypoScript angepasst werden:

 

page.includeJSlibs >
page.includeJS >
page.includeJSFooterlibs >
page.includeJSFooter >
config.inlineStyle2TempFile = 0

page.includeCSS >
plugin.tx_felogin_pi1._CSS_DEFAULT_STYLE >

 

Du solltest Deinen Code prüfen, ob noch JS oder CSS Dateien eingebunden werden. Manchmal kann das passieren, wenn zusätzliche Extensions Daten über page.headerData oder page.footerData einbinden. Das sollte man in der Regel nicht machen, aber genau dass machen wir gleich ;-). TYPO3 bietet keine Möglichkeit CSS im Footer zu laden. Deshalb lade ich nun meine Dateien auf genau diese Weise. Der Pfad geht auf eine Subdomain die einfach in mein Projekt eingehängt wurde. (Stichwort "Serve static content from a cookieless domain.")

 

page.footerData.9 = TEXT
page.footerData.9.value (
 <link rel="stylesheet" type="text/css" href="http://assets.rutschmann.biz/css/main.min.css" media="all">
)

page.footerData.10 = TEXT
page.footerData.10.value (
 <script src="http://assets.rutschmann.biz/js/main.min.js" type="text/javascript"></script>
)

 

Du solltest nun deine Seite prüfen. Beim laden wird sie vermutlich schrecklich aussehen, das CSS wird ja nun erst am Ende geladen. Bei Google PageSpeed erscheint nach wie vor die Meldung "Inhalte above the fold priorisieren". Jetzt wird es richtig tricky, ich hoffe Du magst Kaffee. Viel Kaffee :-)

Die Aufgabenstellung ist relativ einfach. Mittels 

 

page.cssInline {
 10 = TEXT
 10.value (
 .klasse { /* anweisungen */ } 
 )
}

 

Musst Du nun im Header das CSS so aufbauen, dass die Seite beim laden gut aussieht, und der Fehler bei Google PageSpeed verschwindet. Grundsätzlich geht es darum, dass die Seite im Browserfenster gerendert werden kann, ohne dass das CSS File geladen werden muss. Das gilt nur für den sichtbaren Bereich. Am besten nutzt Du für den Start den "Critical Path CSS Generator" [3], der in der Regel die meiste Arbeit abnimmt. Bei Punkt 1 trägst Du deine URL ein. Bei Punkt 2 kopierst Du das CSS aus dem komprimierten, zusammengefügten CSS File, dass Du mittels grunt (oder manuell) erzeugt hast. Mit Klick auf den schwarzen Button wird Deine Seite analysiert und dass CSS extrahiert, dass Du dann Inline in obigen TypoScript Schnipsel verwenden kannst. Diese Aufgabe kann sehr Zeitraubend sein. Eventuell musst Du Deine Seite auch nochmal komplett zerlegen und einzelne Bereiche einzeln ausgeben (Header, Content, Footer, etc.), um zu sehen, wann der Lösungsvorschlag verschwindet. Dabei solltest Du nicht nur auf das CSS schauen, sondern auch auf eventuell genutztes JavaScript. So hat es bei mir eine kleine Ewigkeit gedauert, bis der FlexSlider auf der Startseite das Rendering nicht mehr blockiert hat.

Ansonsten sollten die weiteren Tips aus dem Slide umegsetzt werden. Die Komprimierung der Bilder, installation von Sourceopt und nc_staticfilecache sollten in betracht gezogen werden. Ich nutze zusätzlich noch die Extension "html_minifier" um den Quelltext klein zu bekommen.

 

[1] András Ottó PageSpeed Insights 100/100 guide (Link auf Slideshhare im zweiten Absatz "Wie das geht")

[2] Google PageSpeed Insights

[3] Critical Path CSS Generator