Mit TYPO3 ⚡ Accelerated Mobile Pages(AMP) nutzen

Javascript Service Worker und Google AMP im Einsatz mit TYPO3

Um AMP in TYPO3 zu integrieren, haben wir eine TYPO3-Extension für das System geschrieben und die Website Birdy.net AMP-fähig gemacht.

Zusätzlich haben wir dort noch einen Serviceworker eingerichtet, damit die Website auch offline erreichbar ist.

Bevor wir zur Extension kommen erstmal eine kurze Erläuterung, worum es sich bei AMP überhaupt handelt:

AMP oder auch Accelerated Mobile Pages (kurz: AMP, dt.: beschleunigte Mobilseiten) ist ein Projekt von Google, das auf folgende Punkte ausgerichtet ist:

  • bessere Nutzererfahrung
  • schnellere Webseiten
  • höhere Erreichbarkeit
  • reduzierter und auch strukturierter Code

Google AMP besteht aus den Modulen AMP HTML, AMP JS, AMP Cache.

AMP HTML

Bei AMP HTML handelt es sich um eine angepasste Version von HTML.
Einige Elemente wie z.B. Bilder, Iframes wurden umgebaut und müssen dementsprechend auch in TYPO3 angepasst werden.

AMP JS

Hierbei handelt es sich um eine Javascript Bibliothek, welche sicherstellt, dass die Website von einem zentralen Punkt geladen wird.

AMP Cache

Der zentrale Punkt von AMP ist der Google AMP Cache. Wie bei einem CDN wird die Webseite von Google geladen. Dieses Vorgehen sorgt dafür, dass die Website ⚡-schnell geladen werden kann.


Die WACON AMP TYPO3-Extension

Bei der Entwicklung der Extension haben wir großen Wert darauf gelegt, dass sich außer dem Quellcode sehr wenig an der Seite verändert. Das Layout der Seite soll möglichst erhalten bleiben.

Zum Vergleich: Birdy.net und Birdy.net AMP


Aufbau der Extension:

Im ersten Schritt haben wir festgelegt, unter welchem Parameter die AMP Version erreichbar sein soll. Wir haben uns für den Parameter "amptype = 1337" entschieden. Zusätzlich entfernen wir im folgenden Snippet die baseURL.

 

[globalVar = GP:amptype = 1337]
   config.baseURL >
   config.linkVars = L, amptype
[global]

 

In Kombination mit der Extension realurl kann der oben genannte Parameter als Teil des Pfades gesetzt werden.

So wird aus der URL: https://www.birdy.net/typo3-agentur-stuttgart.html?amptype=1337 eine "schönere" URL generiert: https://www.birdy.net/amp/typo3-agentur-stuttgart.html

Dadurch, dass auf der Seite sowohl ein Canonical, als auch ein amphtml-Canonical gesetzt ist, ist es kein Problem, dass die Seite unter zwei URLs erreichbar ist. Durch den Canonical wird auf die ursprüngliche HTML-Version (Vermeidung von Duplicate Content) verlinkt und durch den amphtml-Canonical wird auf die richtige AMP-Seite (Info für google, welches die AMP-Seite ist). 

Mit dem Code:

 

'preVars' => array(
         array(
            'GETvar' => 'amptype',
            'valueMap' => array(
               'amp' => '1337'
            ),
            'noMatch' => 'bypass',
         ),
      ),

 

haben wir der Extension realurl mitgeteilt, dass der Parameter amptype="1337" durch /amp/ ersetzt werden soll.


Im nächsten Schritt erweitern wir das HTML-Tag um amp oder dem ⚡-Blitz.

 

config.htmlTag_setParams = none
config.htmlTag_stdWrap {
   setContentToCurrent = 1
   cObject = COA
   cObject {
      10 = TEXT
      #10.value = ⚡
      wrap = <html amp |>
   }
}

 

Nun ist es wichtig, dass man die von AMP vorgegebene Reihenfolge innerhalb des HTML-Headers beachtet.

Um diese Reihenfolge einzuhalten, entfernen wir zunächst alle CSS- und JS-Einbindungen.

 

page {
   includeCSS >
   includeJS >
   includeJSFooter >
   includeJSFooterlibs >
   includeJSLibs >

 

Danach folgen die benötigten Einträge für AMP:

 

headerData {
   5 = TEXT
   5.value (

<script async src="https://cdn.ampproject.org/v0.js"></script>

 

Hier hat man nun die Möglichkeit weitere optionale AMP-Scripte einzubinden:

 

<!-- Forms -->
<script async custom-element="amp-form" src="https://cdn.ampproject.org/v0/amp-form-0.1.js"></script>

<!-- Image-Lightbox -->
<script async custom-element="amp-image-lightbox" src="https://cdn.ampproject.org/v0/amp-image-lightbox-0.1.js"></script>

 

In unserem Beispiel verwenden wir die zusätzlichen Komponenten für Formulare und eine Lightbox.

Nun folgt der Viewport:

 

<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">

 

Obwohl es verboten ist, in AMP Stylesheets einzubinden, ist es erlaubt, Schriftarten von white-listed Hosts zu verwenden.

Erlaubte Hosts sind:

Typography.com: https://cloud.typography.com
Fonts.com: https://fast.fonts.net
Google Fonts: https://fonts.googleapis.com
Typekit: https://use.typekit.net
Font Awesome: https://maxcdn.bootstrapcdn.com

Quelle: https://www.ampproject.org/docs/design/responsive/custom_fonts


CSS: Normalise.css und BassCss ersetzen Bootstrap

Im folgenden AMP-Custom-Tag hat man die Möglichkeit eigene CSS-Befehle und auch kleine Bibliothek einzubinden:

<style amp-custom>

AMP lässt insgesamt css-Code mit einer Größe von max. 50 KB zu. Frameworks wie Bootstrap, das eine Größe von 137 KB hat, scheiden damit aus. 

Wir empfehlen stattdessen:

  • Normalize.css
    https://necolas.github.io/normalize.css/
    Normalize.css ist eine HTML5-optimierte Alternative zum herkömmlichen CSS-Reset. Durch die browserübergreifende Standardisierung der HTML-Elemente ist es möglich, konsistenten CSS-Code für alle Browser zu schreiben.

    Des weiteren bietet Normalize.css auch einige bugfix und eine ausführliche Dokumentation.
    Größe von Normalize.css5,91 KB

  • BassCss
    https://unpkg.com/basscss@8.0.2/css/basscss.min.css

    BassCss ist ein minimalistisches CSS Framework, welches viele Funktionen zur Verfügung stellt, aber dennoch sehr viel kleiner als Bootstrap ist. Bei der Integration von AMP war es eine sehr hilfreiche Alternative zu Bootstrap.
    Größe von BassCss9,61 KB

Zusammen benötigen beide CSS_Bibliotheken somit 5,91 KB + 9,61 KB = 15,52 KB

Es bleiben somit noch 34,48 KB für eigenen CSS-Code, der hinter den beiden eingefügt werden sollte.

Nachdem der CSS-Code inkludiert wurde, wird als letztes noch das AMP Grundgerüst übernommen:

 

<!-- #AMP Boilerplate -->
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>

 

 


Da wir an vielen Stellen eine Lightbox benötigen, aber nicht für jede Galerie eine eigene Lightbox benötigt werden, erstellen wir per TYPOSCRIPT ein einziges Lightbox-Element:

 

footerData {
      1337 = TEXT
      1337.value(
         <amp-image-lightbox id="lightbox1"
 layout="nodisplay"></amp-image-lightbox>
      )
   }
}

 

Wie oben schon erwähnt, benötigen AMP-Seiten sowohl einen Canonical als auch einen amphtml-Canonical.
Diesen erstellen wir wie folgt:

 

tmp.ampcanonical >
tmp.ampcanonical = TEXT
tmp.ampcanonical {
   typolink.parameter.dataWrap = {TSFE:id}
   typolink.forceAbsoluteUrl = 1
   typolink.returnLast = url
   typolink.additionalParams.cObject = COA
   typolink.additionalParams.cObject {
      5 = TEXT
      5.value = &amptype=1337
      10 = TEXT
      10.dataWrap = &tx_news_pi1[news]={GP:tx_news_pi1|news}&amptype=1337
      10.if.isTrue.data = GP:tx_news_pi1|news
   }
   wrap = <link href="|" rel="amphtml" />
}

page.headerData.1337 < tmp.ampcanonical
page.headerData.1337.insertData = 1

 

Analog dazu wird der Canonical erstellt:

 

    tmp.canonical >
    tmp.canonical = TEXT
    tmp.canonical {
        typolink.parameter.data = TSFE:id
        typolink.forceAbsoluteUrl = 1
        typolink.returnLast = url
        typolink.additionalParams.cObject = COA
        typolink.additionalParams.cObject {
            5 = TEXT
            5.value = &amptype=0
            10 = TEXT
            10.dataWrap = &tx_news_pi1[news]={GP:tx_news_pi1|news}
            10.if.isTrue.data = GP:tx_news_pi1|news
        }
        typolink.addQueryString.exclude = amptype
        wrap = <link href="|" rel="canonical" />
    }

page.headerData.1338 < tmp.canonical
page.headerData.1338.insertData = 1

 

Im Quellcode sieht der Code so aus:

 

<link href="https://www.birdy.net/amp/typo3-seo-stuttgart.html" rel="amphtml" />
<link href="https://www.birdy.net/typo3-seo-stuttgart.html" rel="canonical" />

 

 

Fluid Styled Content an AMP anpassen

Um TYPO3 mit AMP kompatibel zu machen, muss man ein paar weitere Sachen beachten:

  • Inline CSS / Javascript fällt weg
  • Weder <img>, noch <iframe> Tags dürfen mehr verwendet werden.

Zusätzlich zum Umbau der Fluid-Elemente ist es auch empfehlenswert die Extension Mask zu verwenden.
Das besondere an der Mask-Extension ist es, dass man dort eigene Inhaltselemente erstellen kann.
So hat man dort zum Beispiel die Möglichkeit einen „Switch“ einzubauen.

Beispiel Birdy.net:
Auf der Seite Kontakt haben wir ein Inhaltselement namens „2-Inhalte“ dieses Inhaltselement stellt jeweils 2 Inhalte nebeneinander dar. Die Besonderheit an diesem Element ist, dass man dort zusätzlich die Möglichkeit hat ein AMP-Element anzugeben.

Der Iframe auf dieser Seite wird über ein HTML-Element eingebaut d.h. Fluid hat an dieser Stelle keinen Einfluss, wie das „Gerüst“ für das Iframe-Element erstellt wird.
Durch diesen automatischen Wechsel zu den AMP-Elementen in Mask, konnte dort ganz einfach ein AMP-Iframe platziert werden.

 

[globalVar = GP:amptype = 1337]
   lib.contentElement {
      templateRootPaths {
         1337 = EXT:amp_extension/ext/fluid_styled_content/Resources/Private/Templates/
      }
      partialRootPaths {
         1337 = EXT:amp_extension/ext/fluid_styled_content/Resources/Private/Partials/
      }
      layoutRootPaths {
         1337 = EXT:amp_extension/ext/fluid_styled_content/Resources/Private/Layouts/
      }
   }
[global]

 

Sobald man sich auf der AMP-Seite befindet, wird durch den Code oben die AMP-Fluid-Styled-Content Version verwendet.

Derzeitig werden unsere Bilder auf folgende Art und Weise erstellt:

 

<amp-img src="{f:uri.image(image:file,width:dimensions.width, height:dimensions.height)}"
         on="tap:lightbox1"
         srcset="
           {f:uri.image(image:file,width:1880m)} 1880w,
           {f:uri.image(image:file,width:1580m)} 1580w,
           {f:uri.image(image:file,width:1080m)} 1080w,
           {f:uri.image(image:file,width:900m)} 900w,
           {f:uri.image(image:file,width:800m)} 800w,
           {f:uri.image(image:file,width:700m)} 700w,
           {f:uri.image(image:file,width:600m)} 600w,
           {f:uri.image(image:file,width:500m)} 500w,
           {f:uri.image(image:file,width:400m)} 400w,
           {f:uri.image(image:file,width:300m)} 300w,
           {f:uri.image(image:file,width:200m)} 200w,
           {f:uri.image(image:file,width:100m)} 100w"
         role="button"
         tabindex="0"
         width="{dimensions.width}"
         height="{dimensions.height}"
         layout="responsive"
         alt="{file.alternative}"
         title="{file.title}"   >
    <noscript>
        <img src="{f:uri.image(image:file,width:dimensions.width, height:dimensions.height)}" width="{dimensions.width}" height="{dimensions.height}" alt="{file.alternative}">
    </noscript>
</amp-img>

 

Erst wird ein Bild in der gewünschten Größe eingebunden und danach werden mehrere Bilder in verschiedenen Größen zur Verfügung gestellt.
Auf einem kleinen Bildschirm z.B. 360x640 wird an dieser stelle nur das Bild mit einer Breite von 300 geladen. So ist es möglich an großen Bildschirmen große Bilder und bei kleinen Bildschirmen kleine Bilder darzustellen.

Für Fomulare wie z.B. Kontaktformulare, ist es nun notwendig, ein eigenes Script zu schreiben. Da Formulare mit der Methode Post nun kein „action“ Attribut mehr enthalten dürfen. Stattdessen wird nun das „xhr-action“ Attribut verwendet. XHR-Action sorgt dafür, dass die Post Anfragen per Ajax versendet werden.

Bei der Verarbeitung ist es wichtig, dass man das CORS für die eigene Domain aktiviert.

header("access-control-allow-credentials:true");
header("access-control-allow-headers:Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token");
header("access-control-allow-methods:POST, GET, OPTIONS");
header("access-control-allow-origin:".$_SERVER['HTTP_ORIGIN']);
header("access-control-expose-headers:AMP-Access-Control-Allow-Source-Origin");
header("amp-access-control-allow-source-origin:https://".$_SERVER['HTTP_HOST']);
header("Content-Type: application/json");
header("Content-Type: application/json");


Service Worker

Zuerst wird die entsprechende AMP-Componente im Header eingebunden:

 

<!-- Serviceworker -->
<script async custom-element="amp-install-serviceworker" src="https://cdn.ampproject.org/v0/amp-install-serviceworker-0.1.js"></script>

 

Und danach wird unser Serviceworker im Body eingebunden

 

<amp-install-serviceworker
  src="https://www.birdy.net/sw.js"
  data-iframe-src="https://www.birdy.net/sw.js"
  layout="nodisplay">
</amp-install-serviceworker>

 

Wie man oben sehen kann, wird der Serviceworker 2x eingebunden.
Dadurch wird sichergestellt, dass der Serivceworker auch geladen wird, wenn die Seite aus dem AMP-Cache geladen wird.

In der Datei sw.js wird angegeben, welche Inhalte wie gecached werden sollen und wie / wann die Inhalte ausgeliefert werden.
Z.B.: Ist es so möglich eine Schriftart einzubinden, diese aus dem Cache zu laden und falls eine neue Version der Schriftart existiert, wird diese automatisch im Hintergrund herunter und in den Cache geladen.
Durch dieses Vorgehen ist es möglich, dass die Ressourcen und Seiten schnell und auch offline erreichbar sind.

sw.js

Damit der Browser auch weiß, für welche Seite der Serviceworker ist, benötigt man noch eine manifest Datei.
In dieser Datei wird angegeben, ab wo der Serviceworker eingesetzt werden soll und enthält weitere Einstellungen.

ampmanifest.json


Nachdem alles komplett eingerichtet ist, kann man auch mal das Google-Lighthouse-Tool über die Seite laufen und sich überraschen lassen.


Solr in AMP integrieren

Nachdem eine Solr-Suche in TYPO3 eingerichtet wurde, muss die Extension noch für AMP angepasst werden.

Um erstmal Formulare in AMP freizuschalten, muss man das entsprechende optionale Script im Header einbinden.

 

<script async custom-element="amp-form" src="https://cdn.ampproject.org/v0/amp-form-0.1.js"></script>

 

Durch diesen Code werden einige für Fomulare benötigte HTML-Elemente in AMP freigegeben.

Eine Liste der erlaubten und verbotenen Elemente finden Sie auf der amp-form-Seite.

 

Weiter oben wurde bereits das Verfahren von Formularen, die mit der Methode "POST" versendet werden, beschrieben. Daher beschränke ich mich hier auf die Methode "GET".

Formulare mit der Methode Get benötigen noch " target='_top' " oder " target='_blank' ", sowie eine action.

 

Da wir nun wissen, wie das Formular aussehen muss, muss jetzt noch Solr angepasst werden.


Solr anpassen

Zunächst kopieren wir die Ordner Templates, Partials und Layouts von Solr (EXT:solr/Resources/Private) in unsere eigene Extension.

Im TYPOSCRIPT unserer AMP-Extension überprüfen wir nun mit GP:amptype = 1337, ob wir uns auf der AMP-Seite befinden.

Als nächstes Passen wir den Pfad der Solr Templates an und erhöhen die angezeigten Resultate pro Seite.

 

[globalVar = GP:amptype = 1337]
plugin.tx_solr {
   view {
      templateRootPaths {
         0 = EXT:solr/Resources/Private/Templates/
         10 = {$plugin.tx_solr.view.templateRootPath}
         1337 = EXT:amp_extension/ext/solr/Templates
      }
      partialRootPaths {
         0 = EXT:solr/Resources/Private/Partials/
         10 = {$plugin.tx_solr.view.partialRootPath}
         1337 = EXT:amp_extension/ext/solr/Partials
      }
      layoutRootPaths {
         0 = EXT:solr/Resources/Private/Layouts/
         10 = {$plugin.tx_solr.view.layoutRootPath}
         1337 = EXT:amp_extension/ext/solr/Layouts
      }
      results {
         resultsPerPage = 999
      }
   }
[global]

 

Dadurch, dass die Resultate pro Seite erhöht wurden, muss nicht noch zusätzlich das Formular der Paginierung angepasst werden.

Im nächsten Schritt müssen die Styles und Inline-Styles von Solr entfernt werden.

Da wir uns auf einer AMP-Seite befinden und keine CSS / JS-Dateien eingebunden sein dürfen, kann hier, wie auch bereits weiter oben beschrieben, ruhig die Tabula-rasa-Methode verwendet werden.

 

Page {
includeCSS >
includeJS >
includeJSFooter >
includeJSFooterlibs >
includeJSLibs >
}

 

Um nun die Inline-Styles zu entfernen, müssen die entsprechenden Layouts, Templates und Partials überprüft und angepasst werden.

Nachdem die Templates angepasst sind, kann die Suche auf allen Seiten eingebunden werden.

Wir haben die Suche in unserem Partial „Header“ über folgenden Code eingebunden:

 

<form id="tx_indexedsearch_header" class="sm-col-10 md-col-3 col-md-3 mx-auto" target="_top" method="get" action=" /amp/suche.html?tx__%5Baction%5D=search&amp;tx__%5Bcontroller%5D=Standard&amp;cHash=9e404401c5fe19477e203edbb1573766">
   <div>
      <input name="__referrer[@extension]" value="" type="hidden">
      <input name="__referrer[@controller]" value="Standard" type="hidden">
      <input name="__referrer[@action]" value="index" type="hidden">
      <input name="__referrer[arguments]" value="YTowOnt9f59f75f98d2460dee16b70c133155ba3f373cc80" type="hidden">
      <input name="__referrer[@request]" value="a:3:{s:10:&quot;@extension&quot;;N;s:11:&quot;@controller&quot;;s:8:&quot;Standard&quot;;s:7:&quot;@action&quot;;s:5:&quot;index&quot;;}d1079566aa6af8663bd5d1610c3f2064c2667031" type="hidden">
      <input name="__trustedProperties" value="a:1:{s:7:&quot;tx_solr&quot;;a:1:{s:1:&quot;q&quot;;i:1;}}266e0c7b0d1cb357f2e3c96d7b7641dc32dc2bfd" type="hidden">
   </div>
   <div class="form-group">
      <div class="input-group">
         <input placeholder="Suche" class="form-control col-11" id="tx-indexedsearch-searchbox-sword" name="tx_solr[q]" type="text">
         <span class="input-group-addon col-1">
            <button type="submit" class="btn bgRed suchbutton"></button>
        </span>
      </div>
   </div>
</form>

 

Wenn wir nun auf unserer Seite nach TYPO3 suchen erhalten wir folgendes Ergebnis:

Unsere Solr-Suche im AMP-Format finden Sie unter https://www.birdy.net/amp.html