Nachdem Google vor kurzem die Beta Version des Serverseitigen GTM Containers vorgestellt hat, habe ich diese natürlich sofort auf meiner eigenen Webseite ausprobiert und mit den sich ergebenden Möglichkeiten des serverseitigen Trackings experimentiert. Dabei fiel mir zunächst ins Auge, dass Google mit dem aktuellen GTM server-side Tagging Ansatz einen etwas anderen Weg wählt, als ich erwartet hatte.

Anstatt nur das Data Layer Objekt auf der Client-Seite zu belassen und jegliches Tracking komplett serverseitig durchzuführen, bietet Google in GTM sogenannte Clients, die Tracking Requests im bestimmten Format quasi nur über den eigenen GTM Server weiterleiten. Ein Beispiel hierfür sind die beiden GTM Clients für das normale sowie das neue App+Web Google Analytics Tracking. Hierbei wird die gtag.js Bibliothek weiterhin im Browser Client geladen (optional vom eigenen GTM Server). Anschließend wird der Tracking Code im Client ausgeführt und entsprechende Tracking Requests an den eigenen GTM Server geschickt, der diese an Google weiterleitet.

Da diese Vorgehensweise nicht ganz mit meiner eigenen Idee des serverseitigen GTM Trackings übereinstimmt, habe ich überlegt, wie man das Ganze anders gestalten könnte. Aus meiner Sicht wäre es eleganter, nur ein Data Layer Objekt im Client zu definieren, welches automatisch bei Updates das vollständige Data Layer an den serverseitigen GTM Container sendet. Hier würde dann ein universeller Client alle übergebenen Parameter als Ereignisdaten Variable für Tags verfügbar machen.

Nach einigen Versuchen, hat dieser Ansatz auch sehr gut funktioniert. Ich habe einen entsprechenden "DataLayer Collector" GTM Client erstellt, welcher unter dem Pfad /dlc per POST Request übertragene JSON Daten empfängt und in GTM als Ereignisdaten bereitstellt.


const claimRequest = require('claimRequest');
const getRemoteAddress = require('getRemoteAddress');
const getRequestHeader = require('getRequestHeader');
const getRequestBody = require('getRequestBody');
const returnResponse = require('returnResponse');
const runContainer = require('runContainer');
const setPixelResponse = require('setPixelResponse');
const setResponseHeader = require('setResponseHeader');
const getRequestPath = require('getRequestPath');
const JSON = require('JSON');

const userAgent = getRequestHeader('user-agent');
const remoteIp = getRemoteAddress();
const requestPath = getRequestPath();

if (requestPath === '/dlc') {

  claimRequest();
  const requestBody = getRequestBody();

  var event = JSON.parse(requestBody);


  if (!event.event){
    event.event_name = 'unnamed';
  } else {
    event.event_name = event.event;
    event.event = undefined;
  }

  event.userAgent = userAgent;
  event.remoteIp = remoteIp;

  runContainer(event, () => {
    setPixelResponse();

    const origin = getRequestHeader('Origin');
    if (origin) {
      setResponseHeader('Access-Control-Allow-Origin', origin);
      setResponseHeader('Access-Control-Allow-Credentials', 'true');
    }

    returnResponse();
  });
}


Zusätzlich dazu habe ich ein kleines JavaScript Code Snippet erstellt, welches das DataLayer Objekt im Browser initialisiert und die push() Funktion überschreibt, sodass automatisch der vollständige Status des DataLayers gespeichert wird und dieser außerdem per navigator.sendBeacon() Aufruf direkt an den serverseitigen GTM Container übertragen wird.


<script type="text/javascript">
var dataLayer = [];
dataLayer._currentState = {};
dataLayer._push = dataLayer.push;

dataLayer.push = function (arguments) {
  if( arguments[0] == 'event' && arguments.length >= 2){
    var tmp = {'event':arguments[1]};
    if(arguments.length > 2){
      Object.assign(tmp, arguments[2])
    }
    dataLayer._push(tmp);
    Object.assign(dataLayer._currentState, tmp);
  } else {
    dataLayer._push(arguments);
    Object.assign(dataLayer._currentState, arguments);
  }

  if (navigator.sendBeacon && 'event' in arguments) {
    var url = "https://gtm.stickler.de/dlc";
    var data = JSON.stringify(dataLayer._currentState);
    var status = navigator.sendBeacon(url, data);
  } else {
    // TODO: old browser solution
  }
  if('event_callback' in arguments){
    try { arguments['event_callback'](); } catch(e) {}
  }
}
</script>


Die Lösung ist zusätzlich auch mit dem Einsatz von gtag.js kompatibel und das normale GA Tracking kann somit problemlos parallel stattfinden.

Sobald hoffentlich Google selbst sowie andere Anbieter ihre Protokoll-Spezifikationen offenlegen, könnte man somit jegliches Tracking über den serverseitigen GTM Container erledigen. Beispielsweise könnte man auch sehr einfach im Browser Client die gclid oder dclid aus der Landing-Page von Kampagnen extrahieren und per dataLayer.push() Event an den Server weiterleiten. Diese IDs könnte man dann bequem in einem http-only, secure 1st Party Cookie für das spätere Conversion Tracking speichern, sodass ITP und andere Tracking Protection Systeme kein Hindernis mehr darstellen.


var params = new URLSearchParams(document.location.search);


if( params.has('gclid') ){
  dataLayer.push({'event':'gclidFound', 'gclid':params.get('product')});
}
if( params.has('dclid') ){
  dataLayer.push({'event':'dclidFound', 'dclid':params.get('dclid')});
}


Insgesamt gesehen bietet die server-side GTM Lösung eine sehr interessante Methode, mit der Tracking in Zukunft verlässlicher und mit mehr Kontrolle stattfinden kann. Außerdem kann man die Webseite selbst "entschlacken" und überflüssige Tracking JavaScript Bibliotheken aus dem Code entfernen, was wiederum zu schnelleren Ladezeiten führt.