Single-page sites (or single-page apps) typically have just one page load. When navigating the site, subsequent content is either uncovered from the DOM, where it’s been in a hidden state, or loaded from the server using HTTP requests that do not invoke a new page refresh. This behavior, however, has some implications for Google Analytics tracking, especially when configured via Google Tag Manager.

The crux of the problem is this: When you create a Google Analytics tracker, the URL of the page (without a possible #hash) from when the tracker was created is sent as the value of the Document Location field with every hit that uses this tracker. This is used for a number of things, most significantly attributing the session to the campaign specified by URL parameters such as gclid (AdWords) or utm_source, utm_medium.

Now, on single-page sites you send “virtual” pageviews whenever new content is loaded from the server. The reason this works fine with on-page GA is because you’re always using the same tracker object to send the hits. Google even recommends this in their developer guide. Thus the Document Location field stays the same, and campaigns are attributed correctly.

With Google Tag Manager, every single Universal Analytics Tag that fires on the site creates a new, unique tracker object. This means that the Document Location field is updated with every Tag you fire, which is a problem if the URL changes due to browser history manipulation. Thus you can end up with a situation where the first Universal Analytics Tag has gclid in the URL, attributing the session to AdWords, but the next pageview doesn’t have this in the URL anymore, as you would not include it in the “virtual” pageview path names. Instead, since gclid is no longer in the URL, GA looks at the HTTP referrer of the page to see what the previous page was for attribution. It finds google.com, as you came from the search engine (HTTP referrer is not updated when manipulating the URL with the browser History API). Thus a new session starts with attribution to Google Organic! I’ve dubbed this as the Rogue Referral problem.

There are ways to combat this. David Vallejo’s written a great article on setting the Tracker Name in your GTM Tags. This will effectively work like on-page GA, maintaining the initial value of Document Location throughout the page load. However, there are some risks with the tracker name setting, so I wanted to offer an alternative.

Tip 51: Manually Set Document Location To Prevent Rogue Referrals

The way this works is that you store the initial page URL in a global variable such as dataLayer, and then manually set the Document Location field in all your Universal Analytics tags to use this variable.

The most robust way to do this would be to have the following in the page HTML before the GTM container snippet:

window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
  originalLocation: document.location.protocol + '//' +
                    document.location.hostname +
                    document.location.pathname +
                    document.location.search
});

This would store the original URL of the page (without #hash) into a dataLayer variable named originalLocation. Then, you’d add this to all your Universal Analytics tags by browsing to Fields to Set and adding a new field:

Field name: location
Value: {{Data Layer Variable - originalLocation}}

Here, {{Data Layer Variable - originalLocation}} would be a Data Layer Variable you’ve created, pointing to the originalLocation you store when the page is first loaded.

(UPDATE: Note that if you add the location field, you must also specify the page, or else all pages will use what’s stored in location as the page path sent to GA! If you have a single-page site, you probably already have the page field set to a virtual page path, but if not, you can always use something like:

Field name: page
Value: {{JS - Get Page URL}}

Where the variable {{JS - Get Page URL}} is a Custom JavaScript variable with:

function() {
  return document.location.pathname + document.location.search;
}

This would send the current page pathname with any query parameters as the virtual page path dispatched with your GA Tags. Thank you Brian Clifton for pointing out that query parameters should be sent, too.)

If you can’t or don’t want to edit the page HTML, you can also use Tag Sequencing. First, you would need to create a Custom HTML Tag with the same code as above (enclosed in <script> and </script> tags). Then, you would need to identify the first Universal Analytics Tag that fires on the site. This would typically be a Page View Tag with something like All Pages or some other Page View Trigger attached to it. Then, you’d need to add the new Custom HTML Tag to this Page View Tag’s sequence, by firing it before the Page View Tag.

(UPDATE: Read the following Caveat chapter if you choose to do this all via GTM and not the page template!)

That way the original URL is stored into dataLayer before the Page View Tag fires, and is thus available for all the Universal Analytics tags that fire on the page.

Caveat

If you’re pushing the originalLocation via GTM and not the page template, there might be a race condition between when the originalLocation variable is pushed into dataLayer, and when Tags try to access it. In these cases, analytics.js does not default to the current URL, resulting in a missing Document Location field! To fix this, instead of adding {{Data Layer Variable - originalLocation}} directly to the location field in your GA Tags, you might want to add a Custom JavaScript Variable instead:

function() {
  return {{Data Layer Variable - originalLocation}} || window.location.protocol + '//' + window.location.hostname + window.location.pathname + window.location.search;
}

This returns either {{Data Layer Variable - originalLocation}} or, if that hasn’t been set yet, the current URL without hash.

Summary

If you have a single-page site and you’re sending “virtual” pageviews, you might want to check if you have the rogue referral problem. A quick way to identify it is to use the new User Explorer reports, looking for sessions which start with an AdWords hit, but then quickly turn into a new session with Google Organic as the campaign.

Actually, if you’re using Google Tag Manager and you’re sending virtual pageviews, you will most certainly suffer from the rogue referral problem, unless you’ve set the Tracker Name or the Document Location as instructed in this guide.