How to install Google Optimize in Shopify for optimal performance
Posted by Cameron Shaw on
Background & Motivation
Google Optimize is the new A/B and optimization testing platform, released by Google in April 2017 to the public (coming out of a private beta for Optimization360).
Google Optimize requires an additional module be added to a standard GA tracker in order to detect when experiments should be run, and inject the code necessary to change the page to the variant of the experiment. So, the timeline looks like this:
[Page Load]...[GA ready]...[Optimize ready: experiment [y/n]].. if(y) { add code, then: show }, else { show }
Generally the Optimize code is added to the page a split second after the page finishes loading or is rendered, resulting in what's referred to as a "flicker" as the original (also called "baseline" or "control") version is shown briefly, and then updated to reflect the changes being tested. In some cases, this doesn't matter, in others, it will.
Google provides a JS + CSS snippet to prevent this display flicker, by hiding the main page contents of the page until it hears from Google Optimize that the page is ready to be shown. This can have profound implications on performance, especially when considering what constitutes the page being ready. In our experience, the slower the page is, the larger the negative performance impact of the anti-flicker script. For example, a page that loads in 1 second without the anti-flicker script may gain an additional 300-500ms from the anti-flicker script. However, a page that takes 3 seconds to load may gain a full 1 to 2 seconds on it's time to first render from the anti-flicker script. This is generally due to long scripting files being interpreted and processed before the window.load event is fired. Looking closely at the location of large script files and re-arranging them can help minimize the impact, but the ideal approach in terms of performance is to only include the optimize module on pages where an experiment is being run, and to only include the anti-flicker script on experiments which include changes above the fold. The setup we use for most experiments is coming soon.
In this article I show how to:
- Set up an alternate property in GA for custom tracking.
- Conditionally include the optimize module based on Shopify template
- Trigger custom GA events for arbitrary user activity, track them in Optimize
Please note: unless there are strong performance constraints, we recommend using the setup method outlined here. It's a little bit less performance friendly, but if your internal pages (collection, product, cart, etc) are fairly fast already (<1.5s) then the tradeoff is generally worth it. However, I would always recommend the following method for running experiments above the fold on the home page.
Google Optimize + Shopify: optimizing for speeeeed
Not quite as easy and straightforward as it would seem.
Here's my solution.
I think it's safe to say that almost all merchants using Shopify also use Google Analytics, and that most of those merchants are using the automatic implementation of GA by Shopify via the Shopify Admin dashboard, like so:
There are a couple great reasons to do this. Most relevant to us though, is the Enhanced Ecommerce checkbox (!!), which enables the enhanced ecommerce module to enable goal tracking, funnel visualization, etc. They've been working on this for a bit, and it's come a long way! More on this later.
Google Optimize was released globally recently (out of beta), and their instructions for implementation are simply adding the line that includes 'require' to your UA GA snippet:
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-xxxxxxx-1', 'auto');
ga('require', 'GTM-xxxxxxx');
ga('send', 'pageview');
</script>
However, Shopify stores using the automagic implementation can't do this.
The first solution is simply to re-implement the GA snippet manually in the theme (easy enough) so that you have direct access to it and can add the required code.
HOWEVER, you may notice that the snippet provided by Optimize is just the standard UA implementation, plus the optimize module. So... what about enhanced ecommerce? If we wanted to include it, we'd also have a line like ga('require', 'ec'), which would pull in the module for enhanced ecommerce. You may already have figured it out... but the EC module just allows for the capability to track events and goals, it doesn't come out of the box. Shopify is responsible for generating, formatting, and firing all the events related to the enhanced ecommerce module.
So, if you'd like to implement GA manually in the theme, you either have to stop collecting data for the enhanced ecommerce, or ALSO re-implement all the events you would (still) like to track. If you have historic data here, then it'll probably be important to you to keep things 1-to-1 (e.g. the specific events and formating of labels, actions, etc identical so that new data is put into the same buckets as the old data). However, this is no small feat and not something that I particularly wanted to dive into. Especially considering that there doesn't appear to be a clear or comprehensive reference document for the events that Shopify is tracking, or when they're fired. Reverse engineering these interactions from the existing data and then reimplementing doesn't sound fun.
So, what to do? We definitely want to keep leveraging all the great event tracking that shopify provides out-of-the-box with the enhanced ecommerce module, but we also want to use Optimize.
The best solution I've figured out is to create a secondary property within GA, which has a separate tracking ID like UA-XXXXXXXX-2. We can then implement this tracking code along side the one automagically added by Shopify.
ONE BIG GOTCHA:
Turns out of you leave your primary GA property to be implemented by Shopify and add a secondary one implemented directly through the theme, the new tracking snippet overrides and/or otherwise breaks the shopify implementation. This is because Trackers apparently have names and don't like it when you call them by the wrong one. When creating a tracker, you can optionally pass in a name. All commands without a name will use the default. So, declaring 'create', 'UA-XXXXXXXX-2' actually overrides the automatic implementation since it has the same name!
However, it's easy enough to give your new manual implementation a new name all to itself, the syntax is as follows (mine is called atlas, because why not):
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXXXXX-2', 'auto', 'atlas');
ga('atlas.require', 'GTM-XXXXXXX');
ga('atlas.send', 'pageview');
</script>
Note that the require and send commands have to be updated to target the correct tracker.
With this setup, the original GA implementation is alive and well and all those useful events are being tracked by shopify, and we also have a parallel non-conflicting implementation for Optimize. However, since we'll only be optimizing a few pages at a time and don't want to add unnecessary performance overhead on pages which aren't being tested, I add a conditional liquid wrapper so it will only show on pages which have been given a custom template.
{% if template contains 'optimize' %} <!-- GA/GTM code ---> {% endif %}
However, the one big drawback with this method is that you're no longer able to have Google Optimize automatically optimize experiments for conversion rate or revenue! It doesn't have the insight into your full traffic flow, and isn't able to leverage those enhanced ecommerce events into it's data for measuring experiment performance. I'll show you how to get around this for now, but it's a non-ideal solution. Long term we may have to wait until Shopify includes an automatic Optimize implementation like they did for Enhanced Ecommerce in order to leverage all the data.
Anyways, without the enhanced ecommerce events, your optimize experiment will only show you that you can optimize for pageviews or bounce rate. Garbage. We need a better way to measure the relative performance of page variants.
Solution: You can optimize around specific goals set up in GA. So, just go to your (new) GA property (the one associated with Optimize), and create a new goal (Admin > Goals).
Once this goal is set up, it will appear in the dropdown for Objectives when creating a new experiment.
BOOM. Now we can create and run experiments around a GA event that we define.
The last step is implementing the event in your Shopify theme, so that it fires the event when an add to cart button is clicked.
There are plenty of different ways to do this that are well documented online and I took the most simple/easiest approach since a more robust solution doesn't appear to be warranted, and it seems to be working just fine so far.
I went to the <form> tag on the product.optimize custom template (only place when the optimize module will be loaded), and added inline javascript to a new onsubmit attribute like so:
onsubmit="ga('atlas.send', { hitType: 'event', eventCategory: 'product', eventAction: 'addtocart', eventLabel: 'optimization', eventValue: '{{ product.price | money_without_currency }}' });"
Note that this event must be targeting the atlas tracker, and that I'm prefilling the eventValue via liquid serverside.
Now, on any product which has the template product.optimize, the Optimize module is loaded in, and if the page matches an ongoing experiment (e.g. based on url matching), then it will roll the dice and show the user one of the variants. Regardless of variant shown, it will fire the event if the user clicks the add to cart button. But, the optimize module knows which variant was being shown, and keeps up with which variants are getting more events fired. So we have laser-specific measurement for user engagement that we define.
There ya have it! Happy optimizing :)
Update1: the astute reader may have noticed that the goal I am tracking has eventCategory 'products' while the event I fire has Category 'product'. This resulted in 0 conversions being tracked for the first several days. Attention to detail ya'll.
Update2: eventValue only accepts an integer as a valid value. {{ product.price | money_without_currency }} will return 24.99 for a $24.99 product. This results in the event not being tracked at all. Solution: switch to {{ product.price | money_without_currency | floor }} to round down to the nearest integer.
Update3: When setting up the goal in Google Analytics, you set a filter for which events should be counted as a conversion. There is a button for 'Use event value as conversion value'. If this is checked, you must enter a value in the box. If it compares a non-zero number to a presumably null value, the filter doesn't match, and the event is not considered a conversion. Entering a zero corrects this issue.
Update4 (june 2017): added section at the top outlining the background and motivation.
Share this post
- Tags: cro