Früher war alles besser, sollte man meinen. In Bezug auf Ladezeiten einer Webseite trifft dieser Spruch allerdings überhaupt nicht gut zu, denn mittlerweile gehört zu einer Webseite nicht nur eine saubere Ausgabe des Codes und die Kompatibilität zu unterschiedlichen Devices, nein, auch Ladezeiten werden immer wichtiger. Musste man früher (und leider auch heute noch bei vielen Seiten) fünf, zehn oder gar zwanzig Sekunden warten bis sich der Inhalt der Begierde zeigt, hat heute keiner mehr Lust, so lange auf eine Webseite zu warten. Diesen Umstand haben auch die Betreiber der großen Suchanbieter erkannt, und strafen Webseiten, welche zum laden zu lange brauchen, ab.

Um auf die Optimierung einer Webseite einzugehen, und zu zeigen, was mit TYPO3 doch so alles möglich ist, habe ich bereits einen Artikel zum Thema TYPO3 und Google Page Speed geschrieben. In diesem Artikel geht es nun um die Erweiterung der Optimierung von damals. Gerade für jene Webseitenbetreiber deren Besucher auch aus dem Ausland kommen, wird das Thema Content Delivery Network recht schnell interessant. Denn wenn eine Seite schnell zum Beispiel Deutschland aufgebaut wird, heisst das noch lange nicht, dass es genauso in Übersee ist. Und genau hier kommen die stärken eines Content Delivery Network zum Einsatz, denn die Daten sind meist nicht nur auf einem Server der irgendwo steht gespeichert, sondern auf vielen Servern weltweit. Jeder Besucher bekommt die Daten dann von seinem nächstgelegenen Server ausgeliefert. Dass ein Bild schneller auf einem amerikanischen PC landet, wenn es auch von einem amerikanischen anstelle eines deutschen Server gesendet wird, dürfte dahingehend jedem klar sein.

Ein weiterer Vorteil bei der Nutzung eines Content Delivery Network besteht in der Reduzierung der Requests die an den Server gesendet werden. Wenn eine Domain aufgerufen wird, muss für jede Datei, die empfangen werden soll, ein Request durchgeführt werden. Leider stehen uns im Browser nicht unendlich viele Requests zur Verfügung, sondern nur sehr wenige. Da aber parallele Requests auf mehrere Domains verteilt kein Problem darstellt, rechnet es sich, Dateien wie CSS, JS und Bilder auf eine andere Domain auszulagern. Während die eigentliche Webseite unter www.example.com aufgerufen wird, werden Bilder, CSS und JS Dateien von cdn.example.com geladen. Der Browser muss nun also nicht mehr alle Requests auf einer Domain durchführen, sondern kann parallel mehrere Domains abgrasen, bis alle Daten geladen sind. Zusätzlich sind die CDN sehr schnell und bieten eine hohe Bandbreite zum Download an. Leider sind gute Content Delivery Network's nicht umsonst zu haben.

Hier ein Überblick, welche Punkte ich im Artikel abarbeiten möchte

  • Kleines Konzept und Ziel
  • Vorbereitende Maßnahmen
  • Einrichtung des AWS S3 Buckets
  • Einrichten des CloudFront CDN
  • Einrichtung der S3/CDN Domains
  • Erweiterung des CDN mit einem Zertifikat (kostenfrei von AWS)
  • Erstellen der Access Credentials
  • Einrichtung und Konfiguration des TYPO3

    • Vorgehensweise im Bestandsprojekt, Einschränkungen und kurioses
    • Auslagern des CSS, JS, Images etc.

  • Erweiterung des Grunt-Deployments für CSS/JS etc.
  • CacheControl und Expires bei Daten im S3 Bucket
  • CloudFront Cache leeren
  • erneutes erreichen des PageSpeed 100/100 mit CDN
  • Weiterführende Links

 

Kleines Konzept und Ziel

In meinem Artikel werde ich alle Dateien, also das CSS, das JavaScript, die Fonts, die Icons, Bilder des Layouts und auch Bilder des Contents in einem Amazon S3 Bucket speichern. Diesen Speicherplatz verwende ich als Origin (Quelle) für CloudFront, um die Daten im weltweiten CDN zur Verfügung zu stellen. TYPO3 wird dabei so konfiguriert, dass der Redakteur Daten direkt im Amazon S3 Bucket speichern kann, und diese in Content Elementen dann auch wieder auswählen kann. Rein theoretisch bräuchte es bei dieser Konstellation keinen fileadmin/ Ordner mehr, da alles im Amazon S3 Bucket gespeichert wird (auch generierte Thumbnails von TYPO3). Wird die Konfiguration abgeschlossen sein, wird von dem Server, auf dem TYPO3 läuft, nur noch das TYPO3 ausgeführt und das HTML für das FrontEnd ausgeliefert. Alle anderen statischen Daten werden über das Content Delivery Network zugespielt.

 

Vorbereitende Maßnahmen

Du benötigst die folgenden Dinge auf jeden Fall, um das geplante Konzept umsetzen zu können: 

  • Eine Webseite, mit TYPO3, am besten auch schon in der aktuellen Version
  • die TYPO3 Extension für den AWS S3 Zugriff aus TYPO3 (zu bekommen im TER, AWS S3 FAL Driver)
  • Zugriff auf die eigene Domain Verwaltung, um CNAME's für die eigene Domain anlegen zu können
  • einen Amazon AWS Account (zu bekommen bei Amazon AWS)
  • sofern Du ein Deployment ins CDN machen möchtest wie ich, SHH Zugriff auf Deine Konsole (installiert muss sein: npm, grunt, grunt-contrib-concat, grunt-contrib-uglify, grunt-contrib-cssmin, grunt-aws-s3)
  • Kaffee (Bier und härtere Sachen sind ebenfalls erlaubt)

 

Einrichtung des AWS S3 Buckets

Zuerst solltest du ein neues Bucket im AWS S3 Bereich erstellen. Den S3 Dienst findest Du in der AWS Management Konsole (https://eu-central-1.console.aws.amazon.com/console/home?nc2=h_m_mc&region=eu-central-1) im Bereich "Storage & Content Delivery"  mit dem Eintrag "S3". Du kannst dem Bucket einen Namen geben, der Name wird im kompletten S3 Space allerdings einmalig sein. Bewährt hat sich einfach der Domain Name den Du auch später nutzen möchtest. Zusätzlich solltest Du die Region auswählen, wo die Daten gespeichert werden sollen. Am besten in Deiner Nähe, da dies entscheidend für die Geschwindigkeit im TYPO3 Backend später sein kann:

Oops, an error occurred! Code: 20170625230539ab6aedf3

 

Erstelle das Bucket und öffne die Eigenschaften, wähle den Bereich "Permissions" aus. Du solltest für dieses Bucket die Permissions auf "Everyone" mit der Eigenschaft "List" auf aktiviert setzen:

Oops, an error occurred! Code: 201706252305390071adf3

 

Mit dem Button "Edit bucket policy" musst Du an dieser Stelle noch eine weitere Policy hinterlegen:

{
    "Version": "2012-10-17",
    "Id": "Policy234234251123",
    "Statement": [
        {
            "Sid": "Stmt12313423123",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObjectVersionAcl",
                "s3:GetObjectVersionTorrent",
                "s3:GetObject",
                "s3:GetObjectAcl",
                "s3:GetObjectTorrent",
                "s3:GetObjectVersion"
            ],
            "Resource": "arn:aws:s3:::static.example.com/*"
        }
    ]
}

 

Mit dieser Policy werden alle Dateien, die in diesem Bucket sind, öffentlich gemacht. Das ist wichtig, damit auch neue Daten, die über TYPO3 hochgeladen werden, später vom CDN gelesen werden können.

Wichtiger Hinweis: Solltest Du planen, Deine Seite mit dem HTTPS-Protokoll zu betreiben, wäre es natürlich nicht schlecht, auch das CDN später über HTTPS anzubinden, um Warnungen oder geblockte Inhalte, die dann nur über HTTP kommen, zu vermeiden. In diesem Fall solltest Du beim Button "Edit CORS Configuration" folgende Policy hinterlegen:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*.example.com</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
    </CORSRule>
</CORSConfiguration>
 

 

Wie Du sehen kannst, setze ich bereits im AWS S3 Bucket den Access-Control-Allow-Origin für meine Domain. Später werden auch andere HTTP Header direkt hier im AWS S3 Bucket gesetzt, und an den CDN einfach weitergereicht. Das hat den Vorteil, dass ich zum Beispiel die Cache Headers per Datei im S3 setzen kann, ohne im CDN etwas dafür machen zu müssen. Das CDN übernimmt später alle gesetzten HTTP Header. Dazu jedoch später mehr.

Du solltest nun den Bereich "Static Website Hosting" in den Properties öffnen. Du musst die Option "Enable website hosting" aktivieren, und beim "Index Document" zum Beispiel index.html und bei "Error Document" zum Beispiel error.html eintragen. Die anderen Bereiche in den Properties von diesem Bucket sind für den Moment uninteressant.

 

Einrichten des CloudFront CDN

Dir steht nun ein leeres Bucket zur Verfügung. Nun kannst Du ein Content Delivery Network erstellen, welches auf Dein S3 Bucket zurückgreift. Das findest Du in deiner AWS Management Konsole unter dem Eintrag "CloudFront" unter "Storage & Content Delivery" in den AWS Services. Erstelle hier eine neue Distribution mit dem Button "Create Distribution". Du wirst gefragt, was Du eine Distribution erstellen möchtest. Das wäre dann "Web":

 Oops, an error occurred! Code: 2017062523053997254dd1

 

Im nächsten Schritt kommen einige Einstellungsmöglichkeiten auf Dich zu. Am Ende sollte es in etwa so aussehen, eine Erklärung zu den wichtigen Punkten findet sich unter dem Bild:

Oops, an error occurred! Code: 2017062523053914213335

  • Origin Domain Name: Hier kannst Du Dein erstelltes Origin, also das AWS S3 Bucket eintragen. Wichtig hier ist, dass der volle Endpoint angegeben wird (es kommt auch eine Auswahl, wo man Buckets auswählen kann, sofern oben alles richtig gemacht wurde)
  • Viewer Protocol Policy:  Wichtig wenn Du SSL planst. Ich habe diese Einstellung auf "HTTPS Only" stehen. Es dürfen also grundsätzlich nur HTTPS Anfragen an dieses CDN gemacht werden
  • Forward Headers: Ganz wichtig! Stelle das auf "Whitelist um, und füge darunter die bestehenden drei Einträge der rechten Spalte hinzu. Mit dieser Einstellung werden alle HTTP Header die zu einer Datei gehören sollen, durch das CDN an den Browser gesendet. Wenn wir also später ein Expires Datum einer Datei setzen, liefert das CDN diese Einstellung aus dem S3 Bucket genau so aus. Sehr wichtig für PageSpeed im weiteren Verlauf.
  • Compress Objects Automatically: Diese Einstellung auf "Yes" stellen. Diese Option ist für Google PageSpeed ebenfalls sehr wichtig, aber auch ansonsten sehr sinnvoll. Das CDN erkennt bestimmte Dateitypen (css, js, xml etc), und komprimiert diese automatisch mit gzip, sofern der Request des Browser "Accept-Encoding: gzip" mitsendet. Ist das nicht der Fall, wird die Datei vom CDN auch nicht komprimiert.
  • Price Class: Wenn Du weltweite Besucher hast, und überall eine gute Performance haben möchtest, nutzt Du alle "Edges", ansonsten eine Option Deiner Wahl.
  • SSL Certificate: Auch wenn Du planst, SSL zu nutzen, lasse es vorerst auf Default. Ich komme später darauf zurück.

Du hast nun alle Einstellungen entsprechend gemacht und kannst die Distribution erstellen. Danach findest Du sie in der Übersicht der CloudFront Distributionen wieder.

 

Einrichten der S3/CDN Domains

Du hast jetzt einen Bucket, in dem Du die Daten ablegen kannst, sowie eine CloudFront Distribution erstellt. Dein Bucket erreichst Du unter static.example.com.s3.amazonaws.com und in der Liste Deiner CloudFront Distributionen siehst Du den Domain Namen von Deinem Content Delivery Network: gsreshd272hsdad.cloudfront.net. Über diese beiden Domains sind Deine Dateien später erreichbar. Nicht sehr prickelnd, was?

Aus dem Grunde erstellen wir nun für das Bucket und für das CDN jeweils einen CNAME Eintrag. Somit wird dann folgendes daraus:

Das kann sich sehen lassen. Ich habe meine Domains bei df.eu gehostet. Ich muss dazu in die Domaineinstellungen, dort in die Nameservereinstellungen und für meine Domain einen CNAME Eintrag erstellen. Das ist bei jedem Provider anders, bei manchen kommt ein Punkt ans Ende, bei manchen auch nicht:

Oops, an error occurred! Code: 2017062523053935fb73a3

 

Testen des Bucket und des CDN

Nachdem Du für den S3 Bucket und das CDN jeweils den CNAME angelegt hast, solltest Du in kürze mit beiden Subdomains zumindest einen Fehler (AccessError oder NotFound) sehen. Du kannst über die AWS S3 Konsole auch eine Datei (beispiel.png) hochladen. Diese Datei solltest Du dann über die folgenden URL's abrufen können:

 

Du kannst das ganze auch mit curl auf der Konsole prüfen, dann siehst Du gleich die Header:

manfred$ curl -I http://static.example.com/beispiel.png
HTTP/1.1 200 OK
x-amz-id-2: t3bGYS...Sasa
x-amz-request-id: 945F2D5EAB0CACF5
Date: Mon, 30 May 2016 17:35:17 GMT
Last-Modified: Mon, 30 May 2016 17:34:14 GMT
ETag: "29d008c71158760f6f5874e27bb8a4e4"
Content-Type: image/png
Content-Length: 39785
Server: AmazonS3
manfred$

 

Das selbe für das CDN:

manfred$ curl -I https://cdn.example.com/beispiel.png
HTTP/1.1 200 OK
Content-Type: image/png
Content-Length: 39785
Connection: keep-alive
Date: Mon, 30 May 2016 17:40:24 GMT
Last-Modified: Mon, 30 May 2016 17:34:14 GMT
ETag: "29d008c71158760f6f5874e27bb8a4e4"
Accept-Ranges: bytes
Server: AmazonS3
X-Cache: Miss from cloudfront
Via: 1.1 d39xxx56.cloudfront.net (CloudFront)
X-Amz-Cf-Id: 7a2JFxxx4i52A==
manfred$

 

Interessant wäre hier noch der Eintrag X-Cache beim ersten Aufrufen von Curl: "Miss from cloudfront", das besagt, dass die Datei noch nicht im CDN war, und mit diesem ersten Request dann abgelegt wird. Beim zweiten Request schaut es dann so aus:

manfred$ curl -I https://cdn.example.com/beispiel.png
HTTP/1.1 200 OK
Content-Type: image/png
Content-Length: 39785
Connection: keep-alive
Date: Mon, 30 May 2016 17:40:24 GMT
Last-Modified: Mon, 30 May 2016 17:34:14 GMT
ETag: "29d008c71158760f6f5874e27bb8a4e4"
Accept-Ranges: bytes
Server: AmazonS3
X-Cache: Hit from cloudfront
Via: 1.1 d39xxx56.cloudfront.net (CloudFront)
X-Amz-Cf-Id: 7a2JFxxx4i52A==
manfred$

 

 

Einrichten des CDN mit einem Zertifikat (kostenfrei von AWS)

Soweit so gut. Falls Du nicht planst, Deine Seite über SSL und das CDN über SSL (was ich aber empfehlen würde), kannst Du diesen Punkt überspringen.

Sofern Deine Webseite über SSL läuft, macht es natürlich Sinn, auch die restlichen Dateien, die beim laden der Seite dann über das CDN geholt werden, über SSL laufen zu lassen. Es gibt in Deiner AWS Management Konsole unter dem Punkt "Security & Identity" den Eintrag "Certificate Manager". Mit diesem können Zertifikate erstellt und verwaltet werden. Klicke im Certificate Manager auf "Request a certificate, und Du siehst folgende Eingabemöglichkeit:

Oops, an error occurred! Code: 2017062523053912b14204

 

Trage hier Deine künftige CDN Domain ein, und klicke auf "Review and request". Im folgenden klickst Du auf "Confirm and request". Amazon schickt Dir mehrere Approval Emails, in denen Du dich als Domain-Eigner identifizierst und das Zertifikat bestätigst. Die Emails gehen gehen an den technischen Ansprechpartner, der in Deiner Domain hinterlegt ist. Zusätzlich noch an hostmaster@, postmaster@, administrator@, admin@ und webmaster@. Stelle also sicher, dass zumindest eine Emailadresse empfangen werden kann.

Nachdem die Email empfangen wurde, und Du den "I approve" Button geklickt hast, sollte im Certificate Manager der Status auf "Issued" wechseln. Du kannst in der AWS Console zurück in die Übersicht der CloudFront Distributions, Deine angelegte Distribution anklicken und unter dem Reiter "General" den Button "Edit" klicken, um das CDN zu bearbeiten. Im Bereich "SSL Certificate" stellst Du auf "Custom SSL Certificate" um. Hier kannst Du das erstellte und bestätigte Zertifikat auswählen. Ausserdem bekommst Du nun eine neue Option, "Custom SSL Client Support":

Oops, an error occurred! Code: 201706252305390fb0ad5f

 

Wie bereits gesagt, ist das Zertifikat kostenfrei. Und weil es nichts kostet, kann es auch nicht so viel wie ein kostenpflichtiges. Du bekommst nämlich keine eigene IP Adresse für Dein CDN. Folglich muss der Client SNI unterstützen, damit das Zertifikat funktioniert. Welche Browser SNI unterstützen, kannst Du in der Liste der Server Name Indication  Seite bei wikipedia nachlesen. Kurz gesagt, richtige Browser und richtige Betriebssystem können es. Kombinationen wie Windows XP oder IE < 7 gehen dagegen nicht. 

Wenn Du die Distribution speicherst, dauert es einige Minuten bis sie aktiv ist. Danach solltest Du auch über https://cdn.example.com/beispiel.png die Datei laden können.

 

 

Erstellen der Access Credentials

Damit Du aus TYPO3 und eventuell auch mit Grunt Daten in Dein AWS S3 Bucket schreiben kannst, benötigst Du einen Access Key und einen Access Secret Key. In der AWS Management Console findest Du oben rechts Deinen Namen, wenn Du dort drauf klickst, gibt es im DropDown den Eintrag "Security Credentials". Auf der neuen Seite kannst Du im Segment "Access Keys (Access Key ID and Secret Access Key)" Access Keys erstellen, allerdings maximal zwei Stück. Du solltest die ID und den Key also gleich notieren, da der Secret Access Key später nicht mehr einsehbar ist.

 

Einrichtung und Konfiguration des TYPO3

Endlich - TYPO3 kommt an die Reihe. Installiere in Deinem Projekt die Amazon S3 FAL driver (CDN) Extension von anders und sehr. Diese ist derzeit für TYPO3 6.2 LTS und TYPO3 7 LTS freigegeben. Die Extension hat keine weiteren Dependencies und integriert einen FAL Driver ins TYPO3, mit dem Daten in einem AWS S3 Bucket verwaltet werden können. Die Extension beinhaltet bereits den kompletten AWS PHP SDK und stellt (fast) alle Funktionen zur Verfügung. Es gibt leider ein paar kleinere Problemchen und Einschränkungen, auf die ich später noch eingehen werde.

Nachdem die Extension installiert ist, erstellst Du auf oberster Ebene einen neuen Datensatz vom Typ File Storage. Im Reiter "Allgemein" gibst Du einen Namen ein (zum Beispiel Amazon S3) und wählst unter dem Reiter "Konfiguration" den Typ "Amazon S3" aus. Du kannst den Storage wie folgt konfigurieren (Erklärung unter dem Bild):

Oops, an error occurred! Code: 201706252305391b49df24

 

Im Bereich Driver Configuration sind diese Eingaben und Auswahlen wichtig:

  • Amazon S3: Bucket: Hier kommt der Name deines Buckets rein (nicht der Endpoint!)
  • Amazon S3: Region: Hier wählst Du die Region aus, für welche Du den Bucket angelegt hast.
  • Amazon S3: Key: Deine Acces Key ID
  • Amazon S3: Secret Key: Dein Secret Access Key
  • Amazon S3: Public base URL: Hier trägst Du die CDN Subdomain Deiner CloudFront ein. Das ist recht wichtig, da diese URL nachher auch im Quelltext deines TYPO3 erscheinen wird, wenn Du Bilder und Dateien hinterlegst. Damit wird gewährleistet, dass die Daten immer vom CDN übertragen werden.
  • Cache header: max age: Auch dieser Eintrag ist wichtig. Ich habe hier eine Zeitspanne von einem Monat hinterlegt. Das ist der Zeitraum, wie lange eine Datei im CDN gespeichert ist, bevor sie wieder vom AWS S3 Bucket geholt wird. Dazu später mehr.

 

Wenn Du soweit alles eingestellt hast, kannst Du diese Konfiguration speichern. Wenn Du nun in Deinem TYPO3 auf das Modul Dateiliste gehst, siehst Du neben dem fileadmin/ Verzeichnis auch den Amazon S3 Storage:

Oops, an error occurred! Code: 20170625230539e06d62b4

Die kannst diesen Storage genauso nutzen wie den Fileadmin auch. Ordner erstellen, Dateien hochladen und löschen. Vor allem kannst Du diese Dateien später in allen Content-Elementen wieder aus dem Storage auswählen um sie zu benutzen. Die Geschwindigkeit ist richtig gut, ich habe bisher keine großen Verzögerungen im Gegensatz zum lokalen Fileadmin festgestellt.

 

Vorgehensweise im Bestandsprojekt, Einschränkungen und kurioses

Neues Projekt: Wenn Du ein frisches Projekt startest, ist alles im Lot, Du musst einfach nur immer den Amazon S3 Storage benutzen damit alle Daten im CDN sind. Natürlich kannst Du auch parallel den Fileadmin Storage nutzen, um Daten vom lokalen Server zur Verfügung zu stellen.

Bestandsprojekt: Da wird das ganze schon etwas kniffliger. Mit der Integration von FAL in TYPO3 erhält intern jede Datei eine UID und die UID des Storage zugewiesen. Eine einfache Änderung von Fileadmin auf Amazon S3 ist mir bisher nicht bekannt. Das bedeutet, dass Du alle Bilder die gepflegt sind, erst mal in den Amazon S3 Storage kopieren musst, und dann neu zuweisen musst. Wenn Du vorher die Daten nicht immer sauber zuerst in Fileadmin hochgeladen hast, sondern im Element den Direktupload gewählt hast, kann das mitunter stressig werden, da sich dann alle Dateien im Order <Storage>/user_upload/ befinden.

Einschränkungen kopieren: Stand jetzt war es mir nicht möglich, komplette Ordner von Fileadmin in den Amazon S3 Storage über die Filelist zu kopieren. Das Backend motzt wegen fehlender "Write Permission" herum. Vermutlich ein Bug im Storage Driver.

Einschränkungen verschieben: siehe ein Punkt oben

Kurioses: Multiple Upload: sobald ich mehrere Bilder im TYPO3 Backend gleichzeitig hoch lade, bekomme ich beim aktualisieren der Dateiliste eine Exception "_proccessed_/x/x/" is not a resource". Auch hier scheint es einen Bug zu geben, das beim konvertieren der Miniaturansichten im Backend die temporären Ordner nicht zur Verfügung stehen. Der genannte Ordner wird beim nächsten aktualisieren der Dateiliste jedoch erzeugt.

Kurioses: Thumbs im Backend fehlen: Wenn in der Dateiliste die Thumbnails nicht angezeigt werden, könnte dass daran liegen, dass der Pfad zum konvertierten Bild nicht stimmt. Ich hatte dass Problem, dass nach dem Upload von Dateien immer zu viele Slashes im Pfad waren (/_proccessed_//a//1/beispiel.png) Eine Lösung dafür habe ich keine gefunden, darum habe ich den FAL Driver selbst gefixt:

In der Datei

typo3conf/ext/aus_driver_amazon_s3/Classes/Driver/AmazonS3Driver.php

in der Methode getPublicUrl() um Zeile 145 habe ich den Return geändert:

Das hier

 

return $this->baseUrl . '/' . implode('/', $uriParts);

 

wird zu dem hier:

 

return $this->baseUrl . str_replace("//", "/", '/' . implode('/', $uriParts));

 

 

Auslagern des CSS, JS, Images etc.

Nachdem Deine Redakteure nun Bilder und Daten im CDN verteilen, solltest Du auch Deine CSS Dateien, Dein JavaScript, die Bilder für das Template, Fonts und das ganze andere Zeug, das vom Browser angefordert wird, ins CDN auslagern. Du kannst dafür entweder einen neuen Bucket und ein neues CDN mit neuen Subdomains erstellen, um die Requests nochmal weiter zu verteilen und Roundtrips zu verringern, oder Du nutzt das vorhandene, und speicherst dort alle Daten ab. Du kannst Dir im AWS S3 Bucket eine Verzeichnisstruktur erstellen, wie Du sie in Deinem TYPO3 Theme benutzt. Teile die Verzeichnisse am besten sol ein:

  • <BUCKET>/files/css
  • <BUCKET>/files/fonts
  • <BUCKET>/files/images
  • <BUCKET>/files/icons
  • <BUCKET>/files/js

 

Kopiere alle Deine statischen Daten dorthin, und binde sie (css und js Resourcen) entsprechend über TypoScript als externe Resource mit dem vollen Pfad ein. Dabei musst Du darauf achten, dass natürlich nichts mehr lokal konkateniert oder komprimiert wird, da die Dateien ja dann wieder lokal eingebunden werden. Im Quelltext sollte also auf jeden Fall sowas stehen:

<link rel="stylesheet" type="text/css" href="https://cdn.example.com/files/css/main.min.css" media="all">

<script src="https://cdn.example.com/files/js/main.min.js" type="text/javascript"></script>

Sofern Bilder, die im CSS verwendet werden, dort auch richtig referenziert sind, werden diese ebenfalls aus dem CDN geladen.

Bilder, die Du für das Template benötigst und in TypoScript oder in FLUITEMPLATE verwendest, solltest Du natürlich auch extern über das CDN referenzieren.

Wenn sich Deine CSS oder JS Daten ändern, ist der Upload-Vorgang der Dateiennatürlich jedes mal zu wiederholen. Wenn Du Deine Requests verringern möchtest, und die Daten automatisch ins S3 hochladen möchtest, habe ich im Artikel "Pagespeed Insights 100/100 für TYPO3" gezeigt, wie Daten sauber mit grunt konkateniert und komprimiert werden. Dabei werden alle JS und CSS includes zurückgesetzt

 

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

page.includeCSS >
plugin.tx_felogin_pi1._CSS_DEFAULT_STYLE >

 

und die mit grunt erzeugten Daten im Footer als kompletter HTML Tag eingebunden:

 

page.footerData.9 = TEXT
page.footerData.9.value (
<link rel="stylesheet" type="text/css" href="http://cdn.example.com/files/main.min.css" media="all">
)

page.footerData.10 = TEXT
page.footerData.10.value (
<script src="http://cdn.example.com/js/main.min.js" type="text/javascript"></script>
)

 

 Im nächsten Punkt erweitere ich grunt mit der Funktion, diese Daten automatisch ins CDN stellen zu lassen.

 

Erweiterung des Grunt-Deployments für CSS/JS etc.

Im Artikel "Pagespeed Insights 100/100 für TYPO3" habe ich bereits eine komplette Konfiguration gezeigt, wie man JS und CSS Dateien sauber konkateniert und komprimiert (wichtig für PageSpeed 100/100). Wenn Du diese oder eine ähnliche Konfiguration schon am laufen hast, kannst Du grunt auch wunderbar dazu nutzen, um die Template Bilder, CSS Dateien, JS Dateien und andere Ressourcen in dein AWS S3 Bucket speichern zu lassen.

Aufbauend auf meinem vorigen Artikel erweitere ich nun das Gruntfile.js mit dem Upload der Daten.

Zuerst installiere ich das grunt-aws-s3 Modul:

manfred$: npm install grunt-aws-s3
manfred$:

Danach lege ich eine JSON Datei an, welche meine AWS Key ID und mein AWS Secret Key speichert:

manfred$: touch ~/aws-s3.json
manfred$:

Lege diese Datei am besten ins Home Verzeichnis. Nicht in den DocRoot oder in einem von GIT verwalteten Verzeichnis dass eventuell Public geht.

In diese Datei kommen die Access Credentials:

 

{
"accessKeyId": "DEIN_ACCESS_KEY_ID",
"secretAccessKey": "DEIN_SECRET_ACCESS_KEY"
}

 

 

 

Du kannst nun das Gruntfile.js entsprechend anpassen, ich zeige Dir nochmal meine komplette Konfiguration. Erklärung nach dem Code:

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.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-1.5.1.min.js': ['fileadmin/bsdist/theme/js/main.js'],
                }
            }
        },
        cssmin: {
            options: {
                keepSpecialComments: 0
            },
            main: {
                files: {
                    'fileadmin/bsdist/theme/css/main-1.5.1.min.css': ['fileadmin/bsdist/theme/css/main.css']
                }
            }
        },

        aws: grunt.file.readJSON('~/aws-s3.json'),
        aws_s3: {
            options: {
                accessKeyId: '<%= aws.accessKeyId %>',
                secretAccessKey: '<%= aws.secretAccessKey %>',
                region: 'eu-central-1',
                uploadConcurrency: 5, // 5 simultaneous uploads
                downloadConcurrency: 5, // 5 simultaneous downloads
                stream: true,
                access: 'public-read',
                gzip: false
            },
            production: {
                options: {
                    bucket: 'static.example.com',
                    differential: false, // Only uploads the files that have changed
                    gzipRename: 'ext' // when uploading a gz file, keep the original extension
                },
                files: [
                    {
                        expand: true,
                        cwd: 'fileadmin/bsdist/theme/css',
                        src: '*.*',
                        dest: 'assets/css/',
                        action: 'upload',
                        params: {
                            CacheControl: 'public, max-age=2629800',
                            Expires: Math.round(new Date().getTime() / 1000) 2629800
                        }
                    },
                    {
                        expand: true,
                        cwd: 'fileadmin/bsdist/theme/fonts',
                        src: '*.*',
                        dest: 'assets/fonts/',
                        action: 'upload',
                        params: {
                            CacheControl: 'public, max-age=2629800',
                            Expires: Math.round(new Date().getTime() / 1000) 2629800
                        }
                    },
                    {
                        expand: true,
                        cwd: 'fileadmin/bsdist/theme/images',
                        src: '*.*',
                        dest: 'assets/images/',
                        action: 'upload',
                        params: {
                            CacheControl: 'public, max-age=2629800',
                            Expires: Math.round(new Date().getTime() / 1000) 2629800
                        }
                    },
                    {
                        expand: true,
                        cwd: 'fileadmin/bsdist/theme/js',
                        src: '*.*',
                        dest: 'assets/js/',
                        action: 'upload',
                        params: {
                            CacheControl: 'public, max-age=2629800',
                            Expires: Math.round(new Date().getTime() / 1000) 2629800
                        }
                    },
                ]
            },
        },
    });
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.loadNpmTasks('grunt-aws-s3');
    grunt.registerTask('default', ['concat', 'uglify', 'cssmin', 'aws_s3']);
};
  

 

 

  • grunt.initConfig.concat: Alle Dateien konkatenieren, CSS sowohl als auch JS. Wichtig um Requests zu minimieren!
  • grunt.initConfig.uglify: JS Datei komprimieren
  • grunt.initConfig.cssmin: CSS Datei minimieren
  • grunt.initConfig.aws: Hier wird die Datei mit den Keys für AWS S3 gelesen
  • grunt.initConfig.aws_s3: Hier wird der Upload ans Bucket eingestellt. Wichtig ist, dass gzip deaktiviert wird, das das CDN die komprimierung schon automatisch übernimmt. Wie du siehst wird beim Ausführen von grunt alles, was zum Template gehört automatisch ins Bucket hochgeladen. Sehr wichtig ist der Punkt params.CacheControl und params.Expires in den jeweiligen files:{} Nodes. Warum, das erfährst Du im nächsten Punkt.

 

CacheControl und Expires bei Daten im Bucket

Wie das CDN mit dem S3 Bucket aufgebaut ist, dürfte Dir nun klar sein. Um es zusammen zufassen, AWS S3 dient Dir als Dateispeicher, CloudFront als CDN, wobei CloudFront in dieser Konstellation die Daten vom S3 Bucket holt. Wenn Du also kein Expire und kein CacheControl setzt, wird jedes mal, wenn ein Request durchgeführt, die Resource aus dem S3 Bucket geladen. Beim S3 Bucket wird nach Requests abgerechnet. Na, klingelts? :-) Sorge also Dafür, dass die statischen Inhalte immer ausreichend vom CDN gecached werden können, so kannst Du die Anzahl der Requests an S3 verringern und die Kosten dort gering halten. Als neuer Nutzer von AWS erhältst du im ersten Jahr 50GB Datenübertragung sowie 2.000.000 Requests jeden Monat kostenfrei zur Verfügung gestellt. Ein Übersicht des Pricing findest Du als Link am Ende des Artikels.

 

CloudFront Cache leeren

Nachdem die Daten mit den entsprechenden Header beim Aufrufen über die CDN Domain im CDN abgelegt werden, verbleiben Sie auch dort. Es kann also auch gut möglich sein, dass Du die Daten aus dem CloudFront entfernen musst, um die aktuelle Version aus dem AWS S3 Bucket zu holen. Du kannst das recht einfach machen, in dem Du in der AWS Management Console in die Liste Deiner CloudFront Distributionen wechselst. Klicke auf die ID Deiner CloudFront Distribution und öffne den Reiter "Invalidations". Dort gibt es einen Button "Create Invalidation". Es öffnet sich ein PopUp mit einem Eingabefeld. Hier kannst Du gezielt angeben, was aus dem CDN entfernt werden soll. Einige Beispiele werden schon angezeigt. Möchtest Du alles entfernen, reicht die Eingabe eines einzelnen Stern * aus. Mit klick auf "Invalidate" wird das CDN geleert und die Daten beim nächsten Hit aus dem S3 Bucket neu angefordert. Der Vorgang kann einige Minuten dauern.

 

erneutes erreichen des PageSpeed 100/100 mit dem CDN

Tja, dieses Thema hatte ich mir spannender vorgestellt. Wenn Du soweit oben alles befolgt hast (Compress im CDN aktiviert, CacheControl und Expires gesetzt im FAL und im grunt), sollte der PageSpeed nach wie vor bei 100/100 sein :-)

 

Weiterführende Links

Hier nochmal eine Auflistung vieler nützlicher und benötigter Links

Amazon Amazon Web Services AWS CDN CloudFront Content Delivery Network Download Extension komprimierung PageSpeed TYPO3