Het vinden van de grootste knelpunten is het lastigste, maar ook het allerbelangrijkste aspect aan Conversie Optimalisatie. Alles wat daarna komt; zoals het opstellen van een hypothese, doen van een A/B-test, etc, kun je natuurlijk ook verpesten, maar is in verhouding met het vinden van bottlenecks een formaliteit. Immers: als de onderwerpen waar je aan werkt niet de belangrijkste problemen raken, dan kun je je hypothese nog zo correct formuleren, en nog zo statistisch verantwoord testen, maar het echte probleem los je er niet mee op.
Inhoud
Bewezen Methode voor meer succes met CRO - 10 stappen
Elk bedrijf is uniek, dus met best practices alleen kom je er niet; je zult echt de strategie, propositie, positionering en klantwensen moeten doorgronden en combineren om alles eruit te halen. Inzicht in het bezoekersgedrag is hierbij een belangrijk element.
Stel je voor dat je een fiets moet repareren in het aardedonker. Je hebt er nog niet op gereden en kunt hem niet proberen. je ziet niets dus moet je elk werkingsmechanisme afzonderlijk testen. Als je er dan eindelijk achter bent gekomen dat de derailleur niet goed werkt, moet je uit zien te vinden wat de oorzaak is. Na heel lang testen, tasten en je vingers bezeren kom je erachter dat er een nieuwe kabel nodig is. En zie dan maar eens het passende gereedschap te vinden, laat staan de versteller open te krijgen…
Wees gerust, je leest nog steeds over Conversie Optimalisatie. En hieronder lees je hoe je het licht aan zet.
De methode voor beter inzicht in je bottlenecks
In de tijd dat ik een startup had voor een analyticstool voor conversie optimalisatie, hebben we veel geëxperimenteerd met hoe je ecommerce analytics efficienter en effectiever kunt opzetten:
Hieruit kwam een manier naar voren die, hoewel heel simpel en bijzonder effectief, nagenoeg nooit om me heen gebruikt wordt. Daarom deel ik hem hier. Deze manier is ook toe te passen in Google Analytics, bespaart een hoop tijd en levert waardevolle inzichten op. Ik heb dit destijds de Conversie-matrix genoemd maar je mag het natuurlijk ook anders noemen.
Deze methode heb ik al voor meerdere klanten opgezet en het is de assist geweest voor vele succesvolle A/B-tests.
Waarom je je volle potentieel nooit hebt benut - Top-down vs Bottom-up
Wat er meestal bij data-onderzoeken gedaan wordt is dat er een beetje hier en daar geprikt wordt met ideetjes; er wordt dan iets geroepen wat uitggezocht kan worden en op basis van de data die Google Analytics standaard verzamelt, aangevuld met wat events en aangepaste dimensies, wordt een handmatig onderzoek gedaan. Naast dat dit een zeer gefragmenteerd beeld oplevert van het bezoekersgedrag, vereist het veel handmatige handelingen en moet je maar afwachten of je toevallig op de grootste bottlenecks stuit.
Om bijvoorbeeld te weten hoeveel procent van de bezoekers het menu gebruikt, moet eerst een segment gemaakt worden met bezoekers die het menu gebruiken. Daarna zoek je het totaal aantal bezoekers op om het eerste getal hierdoor te delen. Nu weet je het percentage van de totale gebruikers die het menu gebruiken. Als je vervolgens wilt weten hoe dit percentage per apparaatcategorie is, dan moet je dezelfde handeling weer uitvoeren per apparaatcategorie. Hetzelfde voor ingelogde bezoekers, bezoekers met een winkelmandje, etc. En dan weet je nog steeds niet hoe belangrijk de menu-open-ratio is ten opzichte van alle andere potentiele ratio’s die je had kunnen opzoeken.
Zo vind je de belangrijkste bottlenecks bij CRO
Je ziet het probleem: voor elke metric moet je veel handmatige handelingen verrichten. Natuurlijk kun je met blended data werken in Looker Studio maar dat lost het probleem maar voor een klein deel op.
De bedoeling bij CRO is om erachter te komen waar de grootste bottlenecks liggen. Om deze vervolgens op te lossen. De grootste bottleneck kan overal in de customer journey zitten en in potentie zijn er minimaal tientallen metrics waarvoor je de ratio’s kunt uitzoeken. Op al deze metrics kun je weer doorgraven op vele dimensies, wat de potentiele hoeveelheid gevonden bottlenecks op duizenden zet. Daarom is het veel beter om je data-analyse bottom-up te doen.
De Conversiematrix
Denk nog even aan het voorbeeld van de fiets aan het begin: De Conversiematrix zet het licht aan en toont je daarmee in Ă©Ă©n oogopslag waar de pijnpunten zitten:
De conversiematrix is perfect voor conversie optimalisatie want het is een overzicht van het speelveld van je hele website. Op de horizontale as vind je de customer journey(s) en op de verticale as staan de dimensies. Je kunt meerdere sub-journeys hebben, bijvoorbeeld voor het maken van een account of voor het inloggen of retourneren, en de main journey is die waarmee je je brood verdient, dus als je een webshop hebt: van bezoek tot aan het plaatsen van een order.
De metrics en dimensies in de conversiematrix dienen exact jouw specifieke situatie te dekken, denk hier dus goed over na. Als bijvoorbeeld voor jou het bezoek aan een kortingcodepagina veel uitmaakt voor de klantbeleving, maak dan ook een custom dimensie “bezocht-kortingcodepagina”.
Hierna kun je wanneer nodig nog dieper ingaan op knelpunten om duidelijker te weten waar het probleem zit. Doordat alle waarden custom metrics en dimensies zijn sleep je deze in Looker Studio nu eenvoudig in en uit je widgets, zonder dat je nog handmatige berekeningen hoeft te maken. (By the way: als je Looker Studio nog niet gebruikt: ga het gebruiken! Het werkt zoveel handiger dan het zoeken van data in Google Analytics.)
In de bovenstaande animatie wordt ontdekt dat de doorstroomratio van stap 2 naar stap 3 van de Customer Journey ondermaats presteert voor smartphones van het merk Samsung, met browser Chrome. Nu kun je dit verkregen inzicht gebruiken bij gebruikersonderzoek om erachter te komen waarom het daar mis gaat. Het gegeven voorbeeld is meer technisch ingestoken, en door "zachtere" dimensies zoals landingspaginatype of custom klantgerelateerde dimensies in te slepen ontdek je weer heel andere invalshoeken.
Samengevat zorgt de conversiematrix er dus voor dat de bottlenecks boven komen drijven in plaats van dat je alle potentiele combinaties van customer journey-stappen en dimensies handmatig hoeft te analyseren. Dit scheelt ongelooflijk veel werk en maakt in de praktijk meestal het verschil tussen wel of niet de belangrijkste bottlenecks vinden.
Meet belangrijke stappen in de Customer Journey als custom metric in plaats van met een event
Het gebruik van custom metrics heeft veel voordelen; naast dat je ze gemakkelijk in een rapport sleept zonder dat je overal een segment voor hoeft aan te maken, kun je bijvoorbeeld ook gemakkelijk een (lijn)grafiek maken die de ontwikkeling in de tijd weergeeft. Zo kun je eventuele drops snel signaleren en waar nodig actie ondernemen. Maar het grootste voordeel van custom metrics is dat je de waarden kunt weergeven als percentage doorstroom vanaf de voorgaande stap in de customer journey. In plaats van een absoluut getal zie je de relatieve doorstroom, waardoor de prestatie van die stap direct interpreteerbaar en vergelijkbaar wordt. "50% doorstroom" geeft je een veel beter idee over de prestaties dan "120.000 mensen"; voor salesmensen zijn absolute aantallen vaak interessanter, maar voor conversie optimalisatie zijn doorstroompercentages veel informatiever.
Even voor de volledigheid: widgets in Looker Studio bestaat voor wat data betreft uit 2 zaken: metrics en dimensies. Of deze standaard al bestaan of custom zelf toegevoegd zijn maakt voor het gebruik niets uit. Wel moet je rekening houden met de scope van dimensies; voor de Conversiematrix heb je user-scope nodig.
Een verzoek aan een ontwikkelaar voor het meten van een bepaalde actie op de website, resulteert vaak in een event. Dit kan voor incidenteel onderzoek handig zijn, en natuurlijk kun je in Looker Studio filteren op events, maar je krijgt hiermee alleen absolute aantallen en kunt hier geen calculated metrics voor maken. Voor de Conversiematrix is dit dus geen optie.
Zo zet je de Conversie-Matrix op - Stappenplan
- Bepaal de customer journey(s)Pas dit heel specifiek op de situatie toe en doe het verfijnd; sla geen stapjes over. Op bijvoorbeeld een loginpagina kan het selecteren van het veld met de gebruikersnaam een stap zijn, het typen van de gebruikersnaam, het selecteren van het wachtwoordveld, etc tot en met het klikken op de loginknop.
- Bepaal de klantspecifieke custom dimensiesIs er onderscheid in B2B en B2C klanten? Is het assortiment opgedeeld in verschillende klantsegmenten?
- Maak de custom metrics in Google AnalyticsVoor elke stap in de customer journey maak je 1 custom metric met naam journey-[subjourney]-[stap]. Voor de eerste voorbeeldstap in het script zou dat dus worden: journey-main-visit.
- Maak de calculated metrics in Google AnalyticsVoor elke stap in de customer journey maak je 1 calculated metric met naam journey-[step]-rate. De uitkomst van deze berekening is de doorstroomratio van de voorgaande stap naar de genoemde stap.
- Maak de custom dimensies in Google AnalyticsOp basis van de dimensies die je in stap 2 bepaald hebt. Let op dat je user-scope gebruikt.
- Vul het meetscript aan voor elke stap in de customer journeyPer stap kan een andere aanpak nodig zijn om hem met Javascript te detecteren. Een aantal voorbeelden vind je in het meetscript template hieronder.
- Draai het meetscript via Tagmanager.Voeg hier ook een variabele “journeyStep” toe, en een trigger die luistert naar dataLayer-event “journeyStep”. Configureer een tag die op basis hiervan de journeystappen als event naar Google Analytics stuurt.
- Update in Looker Studio de connectie met Google Analytics in TagmanagerOm de nieuwe metrics en dimensions te importeren.
- Maak de dashboards in Looker StudioZet de metrics op een rij, kopieer de tabel en zet in elke tabel een andere dimensie. Bovenaan zet je een tabel zonder dimensie voor een totaaloverzicht.
- Verkneukel je over alle inzichten die je krijgt
De Javascript
Onderstaand script kun je kopieren en aanpassen voor jouw specifieke situatie. Het is steeds een stapje verder geevolueerd op basis van waar ik tegenaan liep bij klanten, en doet in hoofdzaak het volgende:
- Een hiërarchische definitie van de stappen in de customer journey (hier touchpoints genoemd) en een functie die teruggeeft of de stap bereikt is. De stap kan direct bij de pageload bereik worden, maar ook later, bijvoorbeeld bij een buttonclick.
- Een check of er al een journeystage is opgeslagen in localStorage. Als dat het geval is wordt gewerkt vanuit deze status, zoniet dan staan alle stappen op “untouched”
- Als een stap in een journey bereikt wordt en de status van de stap is nog untouched, dan wordt een event naar de dataLayer gestuurd en wordt de huidige journey-state geupdated in localStorage.
- Alle voorgaande stappen in een journey die nog niet bereikt zijn worden ook meegenomen. Het kan op een website nu eenmaal voorkomen dat een meting een keer niet goed doorkomt, en op deze manier wordt een zo zuiver mogelijk beeld gecreëerd om later zo correct mogelijk de doorstroom per stap te kunnen berekenen.
(function () {
// Define touchpoints. Top levels are journeys, with children as steps in that journey. The steps and functions are examples and should be tailored to the specific website.
var touchPoints = {
main: {
domain: 'https://www.domain.nl',
steps: {
visit: {
checker: function () {
return true
},
stepId: 1
},
login_message: {
checker: function () {
return google_tag_manager['GTM-12345678'].dataLayer.get('loginMessage')
} == 'login_message'
},
stepId: 2
},
checkout_step_1: {
checker: function() {
return location.pathname == '/checkout/step1' && productCategory == 'online'
},
stepId: 3
},
checkout_step_2: {
checker: function() {
return location.pathname == '/checkout/step2' && productCategory == 'online'
},
stepId: 4
},
},
login: {
domain: 'https://login.domain.com',
steps: {
login_page: {
checker: function() {
return location.pathname == '/login'
},
stepId: 1
},
types_mail_address: {
checker: function() {
var mailField = document.querySelector('input#username');
if (mailField && mailField.checkValidity()) {
updateTouchPoint('login', 'types_mail_address');
} else if (mailField && virtualPagePath == '/login') {
mailField.addEventListener('input', function () {
updateTouchPoint('login', 'types_mail_address')
}, {
once: true
});
}
},
stepId: 2
},
mail_recognized: {
checker: function() {
return document.querySelector('input#password') && virtualPagePath == '/login'
},
stepId: 3
},
types_password: {
checker: function() {
var passField = document.querySelector('input#password');
if (passField && virtualPagePath == '/login') {
passField.addEventListener('input', function () {
updateTouchPoint('login', 'types_password')
}, {
once: true
});
}
},
stepId: 4
},
login_complete: {
checker: function() {return location.pathname == '/login-success'},
stepId: 5
}
}
}
};
// Only continue if dataLayer exists and localStorage is supported
if (dataLayer && localStorageSupported()) {
// Update version to overwrite older local storage files when the touchPoints variable has been updated
var version = 1;
// Customer specific variables
var virtualPagePath = google_tag_manager['GTM-12345678'].dataLayer.get('virtualPagePath');
var domain = document.location.origin;
// Get current journey stage if availble in local storage
try {
if (localStorage.getItem('track_journeyStage-' + version)) {
var journeyStage = JSON.parse(window.localStorage.getItem('track_journeyStage-' + version));
for (var i = 0; i < journeyStage.length; i++) {
touchPoints[journeyStage[i].sub].steps[journeyStage[i].key].status = true;
}
} else {
// No saved journey info yet -> initiate a ne journey
updateLocalStorage();
}
} catch (error) {
updateLocalStorage();
// Send error event to GA here...
}
// Check all touchpoints on landing
for (var subFunnel in touchPoints) {
if (touchPoints.hasOwnProperty(subFunnel)) {
for (var key in touchPoints[subFunnel].steps) {
if (touchPoints[subFunnel].steps.hasOwnProperty(key) && touchPoints[subFunnel].steps[key].status == undefined && touchPoints[subFunnel].domain == domain) {
if (touchPoints[subFunnel].steps[key].checker()) {
// Touchpoint touched, update status
updateTouchPoint(subFunnel, key);
};
}
}
}
}
}
// Functions
function updateTouchPoint(subFunnel, key) {
// Update touchpoint
touchPoints[subFunnel].steps[key].status = true;
sendMetric(subFunnel, key);
// And set all earlier steps in the subfunnel to true
for (var checkKey in touchPoints[subFunnel].steps) {
if (touchPoints[subFunnel].steps.hasOwnProperty(checkKey)) {
var lowerStepId = touchPoints[subFunnel].steps[key].stepId > touchPoints[subFunnel].steps[checkKey].stepId;
if (touchPoints[subFunnel].steps.hasOwnProperty(checkKey) && touchPoints[subFunnel].steps[checkKey].status == undefined && lowerStepId) { // break
touchPoints[subFunnel].steps[checkKey].status = true;
sendMetric(subFunnel, checkKey);
}
}
}
updateLocalStorage();
}
function updateLocalStorage() {
var sendableArray = [];
for (var subFunnel in touchPoints) {
if (touchPoints.hasOwnProperty(subFunnel)) {
for (var key in touchPoints[subFunnel].steps) {
if (touchPoints[subFunnel].steps.hasOwnProperty(key) && touchPoints[subFunnel].steps[key].status) {
var sendableObject = {};
sendableObject.key = key;
sendableObject.sub = subFunnel;
sendableArray.push(sendableObject);
}
}
}
}
window.localStorage.setItem('track_journeyStage-' + version, JSON.stringify(sendableArray));
}
function sendMetric(subFunnel, metric) {
var stepName = 'journey_' + subFunnel + '_' + metric;
dataLayer.push({
event: 'journeyStep',
journeyStep: stepName
});
}
function localStorageSupported() {
var support = false;
try {
var rand = Math.random();
support = typeof window.localStorage !== "undefined" &&
(localStorage.test_support = rand),
(localStorage.test_support === rand);
localStorage.removeItem("test_support");
} catch (e) {
support = false;
}
if (support) {
return true;
}
};
}) ();
Dingen om rekening mee te houden
- De tracking gebeurt op user-basis; er wordt over alle sessies per gebruiker gekeken of hij bepaalde journey stappen heeft bereikt. Dat maakt deze methode vooral geschikt voor het onboarden van nieuwe klanten, en minder geschikt voor het optimaliseren op bestaande, terugkerende klanten die de flow al een keer doorlopen hebben. Als je de conversie voor terugkerende klanten wilt optimaliseren, zou je alles op sessie kunnen doen; dus gebruik dan sessionStorage in plaats van localStorage, en werk in Google Analytics met sessies in plaats van users.
- De data die verzameld wordt is niet perfect; het kan bijvoorbeeld gebeuren dat iemand op zijn telefoon de eerste stappen van een journey doorloopt, en daarna op zijn laptop nog een keer de hele journey. Dit maakt niet uit; het gaat er om dat je opvallende uitschieters van de doorstroom per dimensiewaarde in journeystappen vindt, en dan is een globaal beeld genoeg.
Heel veel succes en plezier met het optimaliseren van je website!