{"id":106166,"date":"2025-02-24T01:16:22","date_gmt":"2025-02-24T01:16:22","guid":{"rendered":"https:\/\/peraltafinancing.com\/analytics\/track-browsing-behavior-in-google-analytics\/"},"modified":"2025-02-24T01:16:22","modified_gmt":"2025-02-24T01:16:22","slug":"track-browsing-behavior-in-google-analytics","status":"publish","type":"post","link":"https:\/\/fivemor.com\/?p=106166","title":{"rendered":"Track Browsing Behavior In Google Analytics"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<blockquote>\n<p><strong>Last updated 22 May 2019<\/strong>: Added expiration of currently open tabs.<\/p>\n<\/blockquote>\n<p>In this article, <a href=\"https:\/\/github.com\/jethron\">Jethro Nederhof<\/a> of <a href=\"https:\/\/www.snowflake-analytics.com\/about\/\">Snowflake Analytics<\/a> fame and I will introduce you to some pretty neat web browser APIs. The purpose of these APIs is to find out more about <strong>how the user navigated to the current page<\/strong>, and <strong>what\u2019s going on with their browser tabs<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1319 \/ 478;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/new-browsing-behavior-dimensions.jpg\" title=\"New custom dimensions for browsing behavior\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"478\" width=\"1319\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/new-browsing-behavior-dimensions.jpg#ZgotmplZ\" alt=\"New custom dimensions for browsing behavior\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>There are so many things you can do with this new information. You can build <em>proper<\/em> navigational path reports, rather than rely on the fuzzy and often incoherent flow reports in Google Analytics. You can identify how visitors interact with your content using browser tabs &#8211; a crucial bit of information if you want to make heads or tails of Google Analytics\u2019 <a href=\"https:\/\/www.branded3.com\/blog\/understanding-google-analytics-time-page-session-duration\/\">time on page<\/a> metrics, for example. You can see how many redirects were involved in the current navigation action.<\/p>\n<p>The origins of this article are in a number of places, including an impromptu challenge thrown by <a href=\"http:\/\/www.analytics-ninja.com\/\">Yehoshua Coren<\/a> in Twitter (with great contributions from Marek Leci\u00e1n, too):<\/p>\n<p>However, the main source of inspiration came from <a href=\"https:\/\/www.measure.chat\/\">Measure Slack<\/a>, where a similar discussion around tabbed browser was being had:<\/p>\n<div style=\"aspect-ratio: 485 \/ 287;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/measure-slack.jpg\" title=\"Measure Slack\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"287\" width=\"485\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/measure-slack.jpg#ZgotmplZ\" alt=\"Measure Slack\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Jethro\u2019s idea got the ball rolling in my mind, so I contacted him immediately and asked if he wanted to co-author this article. As it turns out, he\u2019d been mulling around lots of different ideas for navigation behavior tracking, so we quickly sketched the outline of this article, and what you are reading now is the final product.<\/p>\n<p>I consider the enhancements introduced in this article just as necessary as those I introduced way back when in my article <a href=\"https:\/\/www.simoahava.com\/analytics\/improve-data-collection-with-four-custom-dimensions\/\">Improve Data Collection With Four Custom Dimensions<\/a>. Once you\u2019re done implementing these scripts, I\u2019m sure you\u2019ll agree.<\/p>\n<p>As said, this article is co-authored by Jethro and me. Since I am the editor, all mistakes and errors are entirely my fault, and any snarky comments should be directed at me and not my generous partner in crime (naturally, all snarky comments will be deleted, but I will read them and promise to be insulted).<\/p>\n<p>You can skip directly to the Solutions chapter, but I do recommend reading the Theory first. At the very least, make sure you read the Caveats chapter, because there are a couple of gotchas you need to be aware of if implementing this solution.<\/p>\n<p>Ah, damn it. Just read the whole thing, will you?<\/p>\n<p>                <span class=\"simmer\"><br \/>\n  <span class=\"close\">X<\/span><\/p>\n<p>\n    <span class=\"fa fa-md fa-bell\"\/><br \/>\n    <strong>The Simmer Newsletter<\/strong>\n  <\/p>\n<p>\n    Subscribe to the <a href=\"https:\/\/www.simoahava.com\/newsletter\/\">Simmer newsletter<\/a> to get the latest news and content from Simo Ahava into your email inbox!\n  <\/p>\n<p>  <\/span><\/p>\n<h2 id=\"1-the-why-and-how-theory\">1. The why and how (theory)<\/h2>\n<p>The web, by default, is <strong>stateless<\/strong>. Your browsing behavior is confined to the current page only, and it\u2019s difficult to programmatically peer into the past (never mind into the future).<\/p>\n<p>Tools like <a href=\"https:\/\/analytics.google.com\/\">Google Analytics<\/a> try to make sense of this stateless mess by collecting information in a chronological order. You send a <strong>pageview<\/strong>, then you send another <strong>pageview<\/strong>. Google Analytics interprets this as navigational behavior where you first viewed the first page and then the second page. Makes sense, right?<\/p>\n<p>But what about if you opened the second page <strong>in a tab<\/strong> and never even glanced at it? GA still interprets it as a page being viewed.<\/p>\n<p>What about if you <strong>reloaded<\/strong> the current page? GA still interprets it as a navigational step.<\/p>\n<p>What about if you pressed the <strong>back button<\/strong> of the browser to return to the previous page? Google Analytics makes no distinction &#8211; it doesn\u2019t know if you clicked a link, typed the URL in the address bar, or used the back button. It\u2019s all the same.<\/p>\n<div style=\"aspect-ratio: 1560 \/ 661;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/behavior-flow.jpg\" title=\"Google Analytics' Behavior Flow report\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"661\" width=\"1560\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/behavior-flow.jpg#ZgotmplZ\" alt=\"Google Analytics' Behavior Flow report\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The <a href=\"https:\/\/support.google.com\/analytics\/answer\/2785577?hl=en\">flow reports<\/a> try to make sense of this mess by aligning everything into a navigational pattern (the \u201cflow\u201d). But the reports are quite difficult to interpret, as they sometimes show sequences that you are certain shouldn\u2019t be possible, lack necessary detail, and they don\u2019t really give you a robust way to query the information or to build a proper flow report yourself.<\/p>\n<p>And tabbed browsing is <strong>still<\/strong> a problem. Each tab initiates a new \u201csession\u201d in the browser (not to be confused with a Google Analytics session). Thus when GA tries to explain to you that everything is part of a linear navigational flow, the truth is actually more complex: each tab initiates a new <strong>branch<\/strong> of navigation!<\/p>\n<div style=\"aspect-ratio: 1319 \/ 578;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/linear-tabbed-flow.jpg\" title=\"Linear vs. branching flow\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"578\" width=\"1319\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/linear-tabbed-flow.jpg#ZgotmplZ\" alt=\"Linear vs. branching flow\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Add to this the fact that in Google Analytics, \u201cReferral\u201d is a reserved term for campaign attribution. Thus when you are navigating around the site, the referring page is not sent when navigating in-site. This would be really helpful when deciphering navigational paths, since the <strong>Previous Page Path<\/strong> dimension does <strong>not<\/strong> necessarily represent the previous page viewed (just the previous page for which a pageview was sent). To make some sense out of the mess, sending the referral string in a Custom Dimension is a good practice.<\/p>\n<div style=\"aspect-ratio: 687 \/ 553;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/referral-report.jpg\" title=\"Referral report\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"553\" width=\"687\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/referral-report.jpg#ZgotmplZ\" alt=\"Referral report\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>There are other, more granular tracking solutions out there, of course. <a href=\"https:\/\/www.simoahava.com\/analytics\/snowplow-full-setup-with-google-analytics-tracking\/\">Snowplow<\/a>, for example, lets you track hit-level data in any way you like. You can include referral information, if you wish. However, since you\u2019re approaching Snowplow with the limitations of SQL queries, as soon as the navigation journey starts looping or has multiple pages, the joins you\u2019d need to make become frustratingly complex and, as a result, quite unwieldy over large amounts of data.<\/p>\n<p>So, we\u2019ve had this problem of analytics tools imposing a linear sequence on a non-linear navigational pattern for a couple of decades, ever since tabbed browsing was introduced around the turn of the millennium.<\/p>\n<p>What we\u2019re proposing in this article is to add another layer to the taxonomy of Google Analytics\u2019 rather rigid schema for browsing behavior.<\/p>\n<p><strong>FROM:<\/strong> <code>Users<\/code> &gt; <code>Sessions<\/code> &gt; <code>Hits<\/code><br \/><strong>TO:<\/strong> <code>Users<\/code> &gt; <code>Sessions<\/code> &gt; <code>Tabs<\/code> &gt; <code>Hits<\/code><\/p>\n<p>This browsing behavior would be encoded directly into <strong>Custom Dimensions<\/strong>, so you don\u2019t have to infer behavior from GA\u2019s pageview model &#8211; rather, you can query the navigational information directly!<\/p>\n<h3 id=\"11-the-performancenavigation-api\">1.1. The PerformanceNavigation API<\/h3>\n<p>The API we\u2019re going to use to identify browsing behavior is called <code>PerformanceNavigation<\/code>. It comes with two read-only properties: <code>performance.navigation.type<\/code> and <code>performance.navigation.redirectCount<\/code>.<\/p>\n<p>The origin story of this API is firmly rooted in the difficulty of measuring performance of any given web page load. Redirects, network lag, and cached resources (locally, server-side, in CDNs) all contribute to the complexity of what the aggregate <strong>performance<\/strong> of any given page load is.<\/p>\n<div style=\"aspect-ratio: 801 \/ 536;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/performance-timing.jpg\" title=\"PerformanceTiming events\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"536\" width=\"801\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/performance-timing.jpg#ZgotmplZ\" alt=\"PerformanceTiming events\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>To help understand this complexity, web browsers implement the <code>PerformanceNavigation<\/code> API. The two properties exposed by the API can be found under the global <code>window.performance.navigation<\/code> interface, and they contain the following information:<\/p>\n<table>\n<thead>\n<tr>\n<th>Parameter<\/th>\n<th>Detail<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>redirectCount<\/code><\/td>\n<td>&#8211;<\/td>\n<td>The number of <code>3XX<\/code> HTTP redirects the browser went through when loading the page.<\/td>\n<\/tr>\n<tr>\n<td><code>type<\/code><\/td>\n<td>&#8211;<\/td>\n<td>An integer representing how the page was loaded.<\/td>\n<\/tr>\n<tr>\n<td>\u00a0<\/td>\n<td>0<\/td>\n<td>Normal navigation, typing the URL, clicking a link, opening a bookmark, entering via an app link, etc.<\/td>\n<\/tr>\n<tr>\n<td>\u00a0<\/td>\n<td>1<\/td>\n<td>Page was refreshed \/ reloaded while already open in the browser tab.<\/td>\n<\/tr>\n<tr>\n<td>\u00a0<\/td>\n<td>2<\/td>\n<td>The page was retrieved from the browser history by using the Back or Forward button.<\/td>\n<\/tr>\n<tr>\n<td>\u00a0<\/td>\n<td>255<\/td>\n<td>Any other way to navigate to the page.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The <strong>255<\/strong> value is interesting, because it is quite undocumented. In tests, it seems to only emerge with the Firefox browser, when the page goes through a <strong>client-side<\/strong> refresh, either using <code>window.location.replace()<\/code> or <code><meta name=\"&quot;refresh&quot;\"\/><\/code>. Note that this goes against the W3C specification, which <a href=\"https:\/\/www.w3.org\/TR\/navigation-timing\/#dom-performancenavigation-type\">clearly states<\/a> that client-side redirects <em>should<\/em> be contained within one of the regular navigation types.<\/p>\n<p>Now, even though we mention how this API was probably conceived to give more information about the performance timing metrics, where it <strong>really<\/strong> shines is uncovering the navigational paths the users take through your site.<\/p>\n<h3 id=\"12-browser-storage\">1.2. Browser storage<\/h3>\n<p>Ever since browser tabs were introduced to the delight of site visitors and web browser users, they\u2019ve been a source of annoyance for web analysts.<\/p>\n<p>In Google Analytics, for example, we simply don\u2019t know (by default) what the tab situation is of any given page. We don\u2019t know if the page was opened in a new tab, for one, since you can\u2019t attach listeners to the right-click context menu, and tracking just middle mouse button clicks doesn\u2019t give a comprehensive idea of the scope of the phenomenon.<\/p>\n<div style=\"aspect-ratio: 619 \/ 443;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/context-menu.jpg\" title=\"Context menu\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"443\" width=\"619\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/context-menu.jpg#ZgotmplZ\" alt=\"Context menu\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The beauty of web analytics is that when we can\u2019t track things <em>directly<\/em> (whether a user submitted a form, for example), we can track them <em>indirectly<\/em> (page load of a \u201cthank you\u201d page). So maybe we can use this same indirect approach with browser tabs, too?<\/p>\n<p>If we can assign an identifier to every tab the user opens in the current website, we can identify individual navigation sequences! This can be achieved by checking against a common data store whether the given tab ID exists already, in which case the page load happened in a tab that was already open, and vice versa.<\/p>\n<p>However, thanks again to the statelessness of the web, there is the problem of persistence. We can\u2019t simply use a global JavaScript property to store the tab ID, because that gets demolished when the user unloads the page by reloading or navigating to another page.<\/p>\n<p>Cookies and <code>localStorage<\/code> are \u201ctoo\u201d persistent, because they are only reset when they are manually cleared (<code>localStorage<\/code>) or when they expire (cookies). We only want to store the ID of a tab for as long as that tab exists.<\/p>\n<div style=\"aspect-ratio: 841 \/ 242;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/sessionstorage.jpg\" title=\"Session storage\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"242\" width=\"841\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/sessionstorage.jpg#ZgotmplZ\" alt=\"Session storage\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Enter <code>sessionStorage<\/code>! It\u2019s a browser storage API that is unique to each tab in your web browser. By storing the tab ID in <code>sessionStorage<\/code>, we can always check if the tab already has an ID (existing tab), or if we need to generate a new one (new tab).<\/p>\n<p>Combine this with some <code>localStorage<\/code> logic, where we keep a running tally of how many tab IDs have been generated, remembering to remove any tab ID once that tab is closed, and we can also get a fairly reliable count of tabs open on your website at any given time.<\/p>\n<p>So, now we are getting close to having all the bits and pieces at hand. Once we combine the tabbed browsing metadata with that provided by the <code>PerformanceNavigation<\/code> API, we can get a nice set of Custom Dimensions that expose a great deal of interesting information about how your visitors navigate your site.<\/p>\n<h2 id=\"2-caveats\">2. Caveats<\/h2>\n<p>Oh, there are <strong>plenty<\/strong> of caveats. The solutions below rely on a number of fragile components, which can get easily messed up due to how browsers act differently in certain circumstances.<\/p>\n<h3 id=\"21-cross-domain-browsing\">2.1. Cross-domain browsing<\/h3>\n<p>If your tracking crosses multiple domains, then each domain will get their own tab ID. That\u2019s because <code>sessionStorage<\/code> and <code>localStorage<\/code> are confined to the current domain only. And if your current domain sails between HTTP and HTTPS protocols, then <em>those<\/em> will get their own storages, too. You could fix this by using URL parameters on the domain boundaries to pass the metadata from one domain to the next, but this is something you\u2019ll need to figure out by yourself.<\/p>\n<h3 id=\"22-firefox-and-chrome-go-rogue\">2.2. Firefox and Chrome go rogue<\/h3>\n<p>An additional point of concern is that Firefox and Chrome do not respect the specification that session cookies and <code>sessionStorage<\/code> should only exist for as long as the tab is alive. Once the browser is closed, these session stores should be purged.<\/p>\n<p>On Chrome and Firefox, however, if you open the browser and you have the <strong>Show your windows and tabs from last time<\/strong> (Firefox) or <strong>Continue where you left off<\/strong> (Chrome) settings turned on, the session stores are restored, too! So even though the tab <em>did<\/em> close, by restoring the tab it\u2019s as if the tab was never closed.<\/p>\n<div style=\"aspect-ratio: 1155 \/ 786;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/firefox-chrome-settings.jpg\" title=\"Firefox and Chrome settings\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"786\" width=\"1155\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/firefox-chrome-settings.jpg#ZgotmplZ\" alt=\"Firefox and Chrome settings\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>That sucks. Not much we can do about that.<\/p>\n<p>Also, and this is interesting, when you do restore tabs or continue where you left off, the browser claims that the navigation type was <code>BACK\/FORWARD<\/code>, meaning it equates restoring tabs with using the back or forward button of the browser.<\/p>\n<p>This sucks, too. And there\u2019s not much we can do about this, either.<\/p>\n<h3 id=\"23-firefox-and-its-client-side-refresh-dilemma\">2.3. Firefox and its client-side refresh dilemma<\/h3>\n<p>Then there\u2019s the problem with Firefox implementing <code>OTHER<\/code> as the navigation type if a client-side refresh is done. This is unfortunate, but doesn\u2019t luckily mess things up too bad, because this behavior is contained within this particular navigation type alone.<\/p>\n<p>In addition to all of these, because the whole granularity of distinguishing between <code>BACK<\/code> and <code>FORWARD<\/code> needs to be coded using a programmatically maintained path of pages the user has visited, all it takes is for them to manually clear the <code>sessionStorage<\/code> to break the whole setup (at least, for as long as the tab is open).<\/p>\n<div style=\"aspect-ratio: 897 \/ 203;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/clear-all-dev-tools.jpg\" title=\"Clear all dev tools\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"203\" width=\"897\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/clear-all-dev-tools.jpg#ZgotmplZ\" alt=\"Clear all dev tools\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>That\u2019s why in the beginning of the solution you have the option of NOT tracking the back and forward button presses with detail. In this case, the string <code>BACK\/FORWARD<\/code> is sent to Google Analytics in case either button is pressed. It\u2019s not as detailed but it does still tell you whether the user navigated using these buttons (or, as mentioned above, if the tabs were restored after restarting the browser).<\/p>\n<p>Our hypothesis is that with more data the averages will converge towards a more reliable dataset. So idiosyncrasies such as tabs being restored or users clearing storage will disappear into the data as more and more page views are accumulated.<\/p>\n<h3 id=\"24-browser-support\">2.4. Browser support<\/h3>\n<p>There\u2019s also the matter of browser support, but unlike some <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/PerformanceNavigation\">online resources<\/a> claim, support for <code>PerformanceNavigation<\/code> is pretty well supported in <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Performance\/navigation\">Chrome and Safari<\/a>.<\/p>\n<h3 id=\"25-single-page-apps\">2.5. Single-page apps<\/h3>\n<p>You could implement this solution for single-page apps, but in that case you should add an additional, custom navigation type called <code>VIRTUAL<\/code>, or something similar, if the navigation was a single-page transition and not a proper page load.<\/p>\n<p>Do note that <code>window.performance.navigation.type<\/code> does <strong>not<\/strong> update when the user uses the browser\u2019s Back \/ Forward buttons and the transition is from one single-page app state to another. So you\u2019d need to code the logic for Back \/ Forward use when no page load is recorded.<\/p>\n<h2 id=\"3-solutions\">3. Solutions<\/h2>\n<p>OK, let\u2019s get to the good stuff! In this chapter, we\u2019ll introduce the technical solution in <a href=\"https:\/\/tagmanager.google.com\">Google Tag Manager<\/a> (Custom HTML tag) that orchestrates the whole thing. In addition to that, each sub-section will detail how to send the respective piece of information to Google Analytics as a Custom Dimension. Basically, all you\u2019ll need to edit is your main Page View tag, since that\u2019s the only one that will be sending this data to Google Analytics.<\/p>\n<h3 id=\"31-the-custom-html-tag\">3.1. The Custom HTML tag<\/h3>\n<p>First, <strong>create a new Custom HTML tag<\/strong>, and add the following code within.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><script><![CDATA[\n  (<span style=\"color:#00a\">function() {\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Set to false if you only want to register &#34;BACK\/FORWARD&#34;\n<span style=\"color:#aaa;font-style:italic\">    <span style=\"color:#aaa;font-style:italic\">\/\/ if either button was pressed.\n<span style=\"color:#aaa;font-style:italic\">    <span style=\"color:#00a\">var detailedBackForward = <span style=\"color:#00a\">true;\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Set expiration of tab count in milliseconds. The recommended default is\n<span style=\"color:#aaa;font-style:italic\">    <span style=\"color:#aaa;font-style:italic\">\/\/ 72 hours (259200000 ms). Set to 0 if you don&#39;t want to expire the tab count.\n<span style=\"color:#aaa;font-style:italic\">    <span style=\"color:#00a\">var expireTabs = <span style=\"color:#099\">259200000;\n    \n    <span style=\"color:#00a\">if (!!<span style=\"color:#0aa\">window.Storage) {\n\n      <span style=\"color:#00a\">var openTabs  = JSON.parse(localStorage.getItem(<span style=\"color:#a50\">&#39;_tab_ids&#39;)) || [],\n          tabId     = sessionStorage.getItem(<span style=\"color:#a50\">&#39;_tab_id&#39;),\n          navPath   = JSON.parse(sessionStorage.getItem(<span style=\"color:#a50\">&#39;_nav_path&#39;)),\n          curPage   = <span style=\"color:#0aa\">document.location.href,\n          newTab    = <span style=\"color:#00a\">false,\n          origin\t= <span style=\"color:#0aa\">document.location.origin;\n\n      <span style=\"color:#00a\">var tabCount,\n          redirectCount,\n          navigationType,\n          prevInStack,\n          lastInStack,\n          payload,\n          expiration,\n          newTabId;\n\n      <span style=\"color:#00a\">var clearExpired = <span style=\"color:#00a\">function(tabs) {\n        \n        <span style=\"color:#00a\">if (expireTabs === <span style=\"color:#099\">0) { <span style=\"color:#00a\">return tabs; }\n        <span style=\"color:#00a\">return tabs.filter(<span style=\"color:#00a\">function(tab) {\n          <span style=\"color:#00a\">try {\n\t        expiration = <span style=\"color:#0aa\">parseInt(tab.split(<span style=\"color:#a50\">&#39;_&#39;)[<span style=\"color:#099\">1], <span style=\"color:#099\">10);\n    \t    <span style=\"color:#00a\">return expiration > (<span style=\"color:#00a\">new <span style=\"color:#0aa\">Date().getTime());\n          } <span style=\"color:#00a\">catch(e) {\n            <span style=\"color:#00a\">return <span style=\"color:#00a\">false;\n          }\n        });\n        \n      };\n      \n      <span style=\"color:#00a\">var updateTabExpiration = <span style=\"color:#00a\">function(tabId) {\n        \n        <span style=\"color:#00a\">if (expireTabs === <span style=\"color:#099\">0) { <span style=\"color:#00a\">return tabId; }\n        <span style=\"color:#00a\">try {\n          newTabId = tabId.split(<span style=\"color:#a50\">&#39;_&#39;);\n          expiration = <span style=\"color:#0aa\">parseInt(newTabId[<span style=\"color:#099\">1], <span style=\"color:#099\">10);\n          <span style=\"color:#00a\">if (expiration > <span style=\"color:#00a\">new <span style=\"color:#0aa\">Date().getTime()) {\n            <span style=\"color:#00a\">return tabId;\n          } <span style=\"color:#00a\">else {\n            newTabId = newTabId[<span style=\"color:#099\">0] + <span style=\"color:#a50\">&#39;_&#39; + (<span style=\"color:#00a\">new <span style=\"color:#0aa\">Date().getTime() + expireTabs);\n            sessionStorage.setItem(<span style=\"color:#a50\">&#39;_tab_id&#39;, newTabId);\n            <span style=\"color:#00a\">return newTabId;\n          }\n        } <span style=\"color:#00a\">catch(e) {\n          <span style=\"color:#00a\">return tabId;\n        }\n      \n      };\n      \n      <span style=\"color:#00a\">var getBackForwardNavigation = <span style=\"color:#00a\">function() {\n        \n        <span style=\"color:#00a\">if (detailedBackForward === <span style=\"color:#00a\">false) {\n          <span style=\"color:#00a\">return <span style=\"color:#a50\">&#39;BACK\/FORWARD&#39;;\n        }\n\n        <span style=\"color:#00a\">if (navPath.length < <span style=\"color:#099\">2) {\n          <span style=\"color:#00a\">return <span style=\"color:#a50\">&#39;FORWARD&#39;;\n        }\n\n        prevInStack = navPath[navPath.length-<span style=\"color:#099\">2];\n        lastInStack = navPath[navPath.length-<span style=\"color:#099\">1];\n\n        <span style=\"color:#00a\">if (prevInStack === curPage || lastInStack === curPage) {\n          <span style=\"color:#00a\">return <span style=\"color:#a50\">&#39;BACK&#39;;\n        } <span style=\"color:#00a\">else {\n          <span style=\"color:#00a\">return <span style=\"color:#a50\">&#39;FORWARD&#39;;\n        }\n\n      };\n\n      <span style=\"color:#00a\">var removeTabOnUnload = <span style=\"color:#00a\">function() {\n\n        <span style=\"color:#00a\">var index;\n\n        <span style=\"color:#aaa;font-style:italic\">\/\/ Get the most recent values from storage\n<span style=\"color:#aaa;font-style:italic\">        openTabs = JSON.parse(localStorage.getItem(<span style=\"color:#a50\">&#39;_tab_ids&#39;)) || [];\n        tabId    = sessionStorage.getItem(<span style=\"color:#a50\">&#39;_tab_id&#39;);\n\n        openTabs = clearExpired(openTabs);\n        \n        <span style=\"color:#00a\">if (openTabs.length && tabId !== <span style=\"color:#00a\">null) {\n          index = openTabs.indexOf(tabId);\n          <span style=\"color:#00a\">if (index > -<span style=\"color:#099\">1) {\n            openTabs.splice(index, <span style=\"color:#099\">1);\n          }\n          localStorage.setItem(<span style=\"color:#a50\">&#39;_tab_ids&#39;, JSON.stringify(openTabs));\n        }\n\n      };\n\n      <span style=\"color:#00a\">var generateTabId = <span style=\"color:#00a\">function() {\n\n        <span style=\"color:#aaa;font-style:italic\">\/\/ From https:\/\/stackoverflow.com\/a\/8809472\/2367037\n<span style=\"color:#aaa;font-style:italic\">        <span style=\"color:#00a\">var d = <span style=\"color:#00a\">new <span style=\"color:#0aa\">Date().getTime();\n        <span style=\"color:#00a\">if (<span style=\"color:#00a\">typeof performance !== <span style=\"color:#a50\">&#39;undefined&#39; && <span style=\"color:#00a\">typeof performance.now === <span style=\"color:#a50\">&#39;function&#39;){\n          d += performance.now(); <span style=\"color:#aaa;font-style:italic\">\/\/use high-precision timer if available\n<span style=\"color:#aaa;font-style:italic\">        }\n        <span style=\"color:#00a\">return <span style=\"color:#a50\">&#39;xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx&#39;.replace(<span style=\"color:#099\">\/[xy]\/g, <span style=\"color:#00a\">function (c) {\n          <span style=\"color:#00a\">var r = (d + <span style=\"color:#0aa\">Math.random() * <span style=\"color:#099\">16) % <span style=\"color:#099\">16 | <span style=\"color:#099\">0;\n          d = <span style=\"color:#0aa\">Math.floor(d \/ <span style=\"color:#099\">16);\n          <span style=\"color:#00a\">return (c === <span style=\"color:#a50\">&#39;x&#39; ? r : (r & <span style=\"color:#099\">0x3 | <span style=\"color:#099\">0x8)).toString(<span style=\"color:#099\">16);\n        }) + (expireTabs > <span style=\"color:#099\">0 ? <span style=\"color:#a50\">&#39;_&#39; + (<span style=\"color:#00a\">new <span style=\"color:#0aa\">Date().getTime() + expireTabs) : <span style=\"color:#a50\">&#39;&#39;);\n\n      };\n      \n      <span style=\"color:#00a\">var validNavigation = <span style=\"color:#00a\">function(type, newTab) {\n        \n        <span style=\"color:#aaa;font-style:italic\">\/\/ Return false if new tab and any other navigation type than\n<span style=\"color:#aaa;font-style:italic\">        <span style=\"color:#aaa;font-style:italic\">\/\/ NAVIGATE or OTHER. Otherwise return true.\n<span style=\"color:#aaa;font-style:italic\">        <span style=\"color:#00a\">return !(newTab === <span style=\"color:#00a\">true && (type !== <span style=\"color:#099\">0 && type !== <span style=\"color:#099\">255));\n      \n      };\n\n      <span style=\"color:#00a\">if (tabId === <span style=\"color:#00a\">null) {\n        tabId = generateTabId();\n        newTab = <span style=\"color:#00a\">true;\n        sessionStorage.setItem(<span style=\"color:#a50\">&#39;_tab_id&#39;, tabId);\n      } <span style=\"color:#00a\">else {\n        tabId = updateTabExpiration(tabId);\n      }   \n          \n      openTabs = clearExpired(openTabs); \n\n      <span style=\"color:#00a\">if (openTabs.indexOf(tabId) === -<span style=\"color:#099\">1) {\n        openTabs.push(tabId);\n        localStorage.setItem(<span style=\"color:#a50\">&#39;_tab_ids&#39;, JSON.stringify(openTabs));\n      }\n\n      tabCount = openTabs.length;\n\n      <span style=\"color:#00a\">if (!!<span style=\"color:#0aa\">window.PerformanceNavigation) {\n        navPath = navPath || [];\n        redirectCount = <span style=\"color:#0aa\">window.performance.navigation.redirectCount;\n        <span style=\"color:#aaa;font-style:italic\">\/\/ Only track new tabs if type is NAVIGATE or OTHER\n<span style=\"color:#aaa;font-style:italic\">        <span style=\"color:#00a\">if (validNavigation(<span style=\"color:#0aa\">window.performance.navigation.type, newTab)) {\n          <span style=\"color:#00a\">switch (<span style=\"color:#0aa\">window.performance.navigation.type) {\n            <span style=\"color:#00a\">case <span style=\"color:#099\">0:\n              navigationType = <span style=\"color:#a50\">&#39;NAVIGATE&#39;;\n              navPath.push(curPage);\n              <span style=\"color:#00a\">break;\n            <span style=\"color:#00a\">case <span style=\"color:#099\">1:\n              navigationType = <span style=\"color:#a50\">&#39;RELOAD&#39;;\n              <span style=\"color:#00a\">if (navPath.length === <span style=\"color:#099\">0 || navPath[navPath.length-<span style=\"color:#099\">1] !== curPage) {\n                navPath.push(curPage);\n              }\n              <span style=\"color:#00a\">break;\n            <span style=\"color:#00a\">case <span style=\"color:#099\">2:\n              navigationType = getBackForwardNavigation();\n              <span style=\"color:#00a\">if (navigationType === <span style=\"color:#a50\">&#39;FORWARD&#39;) {\n                <span style=\"color:#aaa;font-style:italic\">\/\/ Only add to navigation if not coming from external domain\n<span style=\"color:#aaa;font-style:italic\">                <span style=\"color:#00a\">if (<span style=\"color:#0aa\">document.referrer.indexOf(origin) > -<span style=\"color:#099\">1) {\n                  navPath.push(curPage);\n                }\n              } <span style=\"color:#00a\">else <span style=\"color:#00a\">if (navigationType === <span style=\"color:#a50\">&#39;BACK&#39;) {\n                <span style=\"color:#aaa;font-style:italic\">\/\/ Only clear from navigation if not returning from external domain\n<span style=\"color:#aaa;font-style:italic\">                <span style=\"color:#00a\">if (lastInStack !== curPage) {\n                  navPath.pop();\n                }\n              } <span style=\"color:#00a\">else {\n                navPath.push(curPage);\n              }\n              <span style=\"color:#00a\">break;\n            <span style=\"color:#00a\">default:\n              navigationType = <span style=\"color:#a50\">&#39;OTHER&#39;;\n              navPath.push(curPage);\n          }\n        } <span style=\"color:#00a\">else {\n          navPath.push(curPage);\n        }\n        sessionStorage.setItem(<span style=\"color:#a50\">&#39;_nav_path&#39;, JSON.stringify(navPath));\n      }\n\n      <span style=\"color:#0aa\">window.addEventListener(<span style=\"color:#a50\">&#39;beforeunload&#39;, removeTabOnUnload);\n      \n      payload = {\n        tabCount: tabCount,\n        redirectCount: redirectCount,\n        navigationType: navigationType,\n        newTab: newTab === <span style=\"color:#00a\">true ? <span style=\"color:#a50\">&#39;New&#39; : <span style=\"color:#a50\">&#39;Existing&#39;,\n        tabId: tabId.replace(<span style=\"color:#099\">\/_.+\/, <span style=\"color:#a50\">&#39;&#39;)\n      };\n\n      <span style=\"color:#aaa;font-style:italic\">\/\/ Set the data model keys directly so they can be used in the Page View tag\n<span style=\"color:#aaa;font-style:italic\">      <span style=\"color:#0aa\">window.google_tag_manager[{{Container ID}}].dataLayer.set(<span style=\"color:#a50\">&#39;browsingBehavior&#39;, payload);\n      \n      <span style=\"color:#aaa;font-style:italic\">\/\/ Also push to dataLayer\n<span style=\"color:#aaa;font-style:italic\">      <span style=\"color:#0aa\">window.dataLayer.push({\n        event: <span style=\"color:#a50\">&#39;custom.navigation&#39;,\n        browsingBehavior: payload\n      });\n    \n    }\n  \n  })();\n<<span style=\"color:#f00;background-color:#faa\">\/script>\n]]><\/script><\/code><\/pre>\n<\/div>\n<p>Do not add any triggers to this tag. Instead, open your <strong>Page View<\/strong> tag, and add this Custom HTML tag into its <a href=\"https:\/\/www.simoahava.com\/analytics\/understanding-tag-sequencing-in-google-tag-manager\/\">tag sequence<\/a> by making the Custom HTML tag fire <strong>before<\/strong> the Page View tag.<\/p>\n<p>Accessing GTM\u2019s data model directly with <code>dataLayer.set<\/code> is necessary if you want to modify Data Layer keys within a tag sequence. However, for the sake of transparency, we\u2019ll also push a <code>dataLayer<\/code> object with a custom event (<code>custom.navigation<\/code>) and the browsing behavior payload.<\/p>\n<div style=\"aspect-ratio: 853 \/ 464;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/tag-sequencing-settings.jpg\" title=\"Tag Sequencing Settings\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"464\" width=\"853\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/tag-sequencing-settings.jpg#ZgotmplZ\" alt=\"Tag Sequencing Settings\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The code in the Custom HTML tag does a number of things.<\/p>\n<ol>\n<li>\n<p>It generates a <strong>tab ID<\/strong> for the page, which is stored in <code>sessionStorage<\/code>. If a new tab ID is thus generated, the page is marked as being in a new browser tab. If a tab ID was already in storage, then the page is flagged as being in an existing tab.<\/p>\n<\/li>\n<li>\n<p>This tab ID is also stored in an array within <code>localStorage<\/code>, where all tab IDs generated on the site are stored. The length of this array is the count of currently open tabs on the domain.<\/p>\n<\/li>\n<li>\n<p>If the user leaves the page, or closes the browser tab, or closes the browser, the tab ID is removed from the array in <code>localStorage<\/code> (thus keeping the count of open tabs accurate).<\/p>\n<\/li>\n<li>\n<p>If the page was loaded by navigating to the URL, navigation type is set to <code>NAVIGATE<\/code>, and the current page is pushed into an array representing the navigation path, stored in <code>sessionStorage<\/code>.<\/p>\n<\/li>\n<li>\n<p>If the page was reloaded, navigation type is set to <code>RELOAD<\/code>, and the navigation path is kept as it is.<\/p>\n<\/li>\n<li>\n<p>If the Back or Forward button was pressed, the script checks if the current page is the penultimate page in the navigation path, in which case navigation type is set to <code>BACK<\/code>. In other cases, navigation type is set to <code>FORWARD<\/code>.<\/p>\n<\/li>\n<li>\n<p>Count of tabs, count of redirects, navigation type, tab ID, and whether the tab was new or existing are all added to Google Tag Manager\u2019s data model, so that the Page View tag can grab these values with Data Layer variables.<\/p>\n<\/li>\n<\/ol>\n<blockquote>\n<p><strong>UPDATE 22 May 2019<\/strong>: I added some extra code to handle expiration. By default, the expiration is 72 hours, so if the storage isn\u2019t interacted with in 72 hours, the current count of open tabs is cleared.<\/p>\n<\/blockquote>\n<p>There are some nuances to the steps listed above, mainly to account for edge cases, such as when navigating <code>BACK<\/code> or <code>FORWARD<\/code> from an external domain. Even with these precautions, there are situations where the solution will fail (see the <a href=\"https:\/\/www.simoahava.com\/analytics\/track-browsing-behavior-in-google-analytics\/#2-caveats\">Caveats<\/a> chapter).<\/p>\n<p>And now that we are adding all this information into Data Layer, it\u2019s time to pick up the metadata and send it to Google Analytics!<\/p>\n<h3 id=\"32-create-the-custom-dimensions-in-google-analyitcs\">3.2. Create the Custom Dimensions in Google Analyitcs<\/h3>\n<p>First, you\u2019ll need to create some Custom Dimensions in Google Analytics.<\/p>\n<p>Create the following Custom Dimensions in Google Analytics\u2019 Property Settings, all in <strong>hit scope<\/strong>, and make note of the index numbers assigned to them.<\/p>\n<ul>\n<li><strong>Redirect count<\/strong><\/li>\n<li><strong>Navigation type<\/strong><\/li>\n<li><strong>Tab type<\/strong><\/li>\n<li><strong>Tabs open<\/strong><\/li>\n<li><strong>Tab ID<\/strong><\/li>\n<\/ul>\n<div style=\"aspect-ratio: 1544 \/ 755;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/custom-dimensions-google-analytics.jpg\" title=\"Custom Dimensions generated in Google Analytics\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"755\" width=\"1544\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/custom-dimensions-google-analytics.jpg#ZgotmplZ\" alt=\"Custom Dimensions generated in Google Analytics\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"33-create-the-data-layer-variables-in-google-tag-manager\">3.3. Create the Data Layer variables in Google Tag Manager<\/h3>\n<p>Next, create the corresponding Data Layer variables in Google Tag Manager. Here\u2019s my setup:<\/p>\n<table>\n<thead>\n<tr>\n<th>Variable name<\/th>\n<th>Value for <strong>Data Layer Variable name<\/strong> field<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>{{DLV &#8211; browsingBehavior.redirectCount}}<\/td>\n<td><code>browsingBehavior.redirectCount<\/code><\/td>\n<\/tr>\n<tr>\n<td>{{DLV &#8211; browsingBehavior.navigationType}}<\/td>\n<td><code>browsingBehavior.navigationType<\/code><\/td>\n<\/tr>\n<tr>\n<td>{{DLV &#8211; browsingBehavior.newTab}}<\/td>\n<td><code>browsingBehavior.newTab<\/code><\/td>\n<\/tr>\n<tr>\n<td>{{DLV &#8211; browsingBehavior.tabCount}}<\/td>\n<td><code>browsingBehavior.tabCount<\/code><\/td>\n<\/tr>\n<tr>\n<td>{{DLV &#8211; browsingBehavior.tabId }}<\/td>\n<td><code>browsingBehavior.tabId<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Here\u2019s an example of what one of the variables would look like:<\/p>\n<div style=\"aspect-ratio: 645 \/ 405;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/data-layer-variable.jpg\" title=\"Data Layer Variable\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"405\" width=\"645\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/data-layer-variable.jpg#ZgotmplZ\" alt=\"Data Layer Variable\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"34-add-the-data-layer-variables-to-your-page-view-tag\">3.4. Add the Data Layer variables to your Page View tag<\/h3>\n<p>Now, open the Page View tag which you have already edited for the Tag Sequence stuff in the beginning of this chapter. Either in a <a href=\"https:\/\/www.simoahava.com\/analytics\/google-analytics-settings-variable-in-gtm\/\">Google Analytics Settings variable<\/a> or by directly editing the tag fields, add the Data Layer variables to their respective indices in the Custom Dimensions list. This is what my setup looks like:<\/p>\n<div style=\"aspect-ratio: 851 \/ 454;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/google-analytics-tag.jpg\" title=\"Google Analytics tag\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"454\" width=\"851\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/google-analytics-tag.jpg#ZgotmplZ\" alt=\"Google Analytics tag\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"35-test-it\">3.5. Test it!<\/h3>\n<p>Now, save the tag, go to Preview mode, and enter your site.<\/p>\n<p><strong>Note!<\/strong> You can\u2019t really use Preview mode to test if the Data Layer variables are populating correctly. Because you are updating the data model in a tag sequence, the method used does not expose the dynamic changes to the Preview mode user interface.<\/p>\n<p>You\u2019ll need to either look at the Network requests directly, or use a tool such as <a href=\"https:\/\/support.google.com\/analytics\/answer\/6277302?hl=en\">Google Tag Assistant recordings<\/a> or <a href=\"https:\/\/chrome.google.com\/webstore\/detail\/google-analytics-debugger\/jnkmfdileelhofjcijamephohjechhna?hl=en\">Google Analytics Debugger<\/a> to check if the data is being sent correctly.<\/p>\n<p>In any case, <strong>if<\/strong> everything is working, then with your Page View tag you should see the Custom Dimensions being populated with the relevant information.<\/p>\n<div style=\"aspect-ratio: 1195 \/ 476;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/google-tag-assistant-recordings.jpg\" title=\"Google Tag Assistant Recordings\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"476\" width=\"1195\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/google-tag-assistant-recordings.jpg#ZgotmplZ\" alt=\"Google Tag Assistant Recordings\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"36-custom-report-in-google-analytics\">3.6. Custom Report in Google Analytics<\/h3>\n<p>A handy way to pull it all together is to create a Custom Report in Google Analytics with these settings, for example:<\/p>\n<div style=\"aspect-ratio: 1378 \/ 613;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/google-analytics-custom-report.jpg\" title=\"Google Analytics Custom Report\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"613\" width=\"1378\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/google-analytics-custom-report.jpg#ZgotmplZ\" alt=\"Google Analytics Custom Report\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>This report will contain interesting information about how users navigated to the different pages on your site.<\/p>\n<div style=\"aspect-ratio: 1243 \/ 508;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/custom-report-ga.jpg\" title=\"Custom Report in GA UI\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"508\" width=\"1243\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/custom-report-ga.jpg#ZgotmplZ\" alt=\"Custom Report in GA UI\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Couple this with things like <a href=\"https:\/\/www.simoahava.com\/analytics\/improve-data-collection-with-four-custom-dimensions\/#3-session-id\">Session ID<\/a> and <a href=\"https:\/\/www.simoahava.com\/analytics\/improve-data-collection-with-four-custom-dimensions\/#4-hit-timestamp\">Hit Timestamp<\/a>, and you can really start digging into how users move from one page to the other on your site.<\/p>\n<h3 id=\"37-other-data-stores\">3.7. Other data stores<\/h3>\n<p>If you\u2019ve taken the time to <a href=\"https:\/\/www.simoahava.com\/analytics\/snowplow-full-setup-with-google-analytics-tracking\/\">duplicate your Google Analytics data to Snowplow<\/a>, you\u2019ll naturally have access to a far more granular, raw hit stream resource for querying against. For example, here\u2019s a simple SQL query output with all the relevant dimensions included:<\/p>\n<div style=\"aspect-ratio: 737 \/ 344;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/04\/sql-query-from-snowplow.jpg\" title=\"Snowplow query in SQL\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"344\" width=\"737\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/04\/sql-query-from-snowplow.jpg#ZgotmplZ\" alt=\"Snowplow query in SQL\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Do note that Snowplow doesn\u2019t differentiate between different <em>scopes<\/em> of Custom Dimensions, since scope is a concept applied in Google Analytics processing. So if you are sending an identifier into a session-scoped Custom Dimension (e.g. Session ID), the hit stream to Snowplow will interpret this as hit-scoped data, and thus the identifier is pretty useless.<\/p>\n<h2 id=\"4-summary\">4. Summary<\/h2>\n<p>First of all, I\u2019m hugely indebted to Jethro Nederhof for agreeing to draft this solution with me. I really love the community of analytics developers &#8211; seems like everyone is giddy with excitement when figuring out new solutions to age-old questions, and the amount of knowledge being shared across blogs, Slack channels, and social media is a testament to the selflessness of these good men and women.<\/p>\n<p>In my humble opinion, anyone interested in proper page navigation analysis should try out this solution. Understanding things like browser tab usage and Back \/ Forward browsing can help you figure out where the information architecture blind spots of your site are or whether you need to fix the navigation options you offer your visitors, for example.<\/p>\n<p>But, as always on this particular blog, this is a technical solution first and foremost. Jethro and I wanted to highlight some cool tricks you can do with the web browser, and we fully expect others to refine these methods even further.<\/p>\n<p>It would be cool if the APIs were developed even further, such as by automatically distinguishing between Back and Forward of the browser. Right now, they\u2019re bunched together which is why we need the workaround of managing the navigation path in <code>sessionStorage<\/code>, and that\u2019s a fragile solution indeed.<\/p>\n<p>Let us know in the comments what you think of this trick, and whether you have suggestions on how to improve it!<\/p>\n<\/p><\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Last updated 22 May 2019: Added expiration of currently open tabs. In this article, Jethro Nederhof of Snowflake Analytics fame and I will introduce you to some pretty neat web browser APIs. The purpose of these APIs is to find out more about how the user navigated to the current page, and what\u2019s going on [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":106169,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12033],"tags":[12765,14246,32764,1531,1018],"dealstore":[],"offerexpiration":[],"class_list":["post-106166","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-analytics","tag-behavior","tag-browsing","tag-google","tag-track"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Track Browsing Behavior In Google Analytics - Som2ny Network<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/fivemor.com\/?p=106166\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Track Browsing Behavior In Google Analytics - Som2ny Network\" \/>\n<meta property=\"og:description\" content=\"Last updated 22 May 2019: Added expiration of currently open tabs. In this article, Jethro Nederhof of Snowflake Analytics fame and I will introduce you to some pretty neat web browser APIs. The purpose of these APIs is to find out more about how the user navigated to the current page, and what\u2019s going on [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fivemor.com\/?p=106166\" \/>\n<meta property=\"og:site_name\" content=\"Som2ny Network\" \/>\n<meta property=\"article:published_time\" content=\"2025-02-24T01:16:22+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/new-browsing-behavior-dimensions.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1078\" \/>\n\t<meta property=\"og:image:height\" content=\"492\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"18 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fivemor.com\/?p=106166#article\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/?p=106166\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\"},\"headline\":\"Track Browsing Behavior In Google Analytics\",\"datePublished\":\"2025-02-24T01:16:22+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=106166\"},\"wordCount\":3545,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=106166#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/new-browsing-behavior-dimensions.jpg\",\"keywords\":[\"Analytics\",\"Behavior\",\"browsing\",\"Google\",\"Track\"],\"articleSection\":[\"Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fivemor.com\/?p=106166#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fivemor.com\/?p=106166\",\"url\":\"https:\/\/fivemor.com\/?p=106166\",\"name\":\"Track Browsing Behavior In Google Analytics - Som2ny Network\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=106166#primaryimage\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=106166#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/new-browsing-behavior-dimensions.jpg\",\"datePublished\":\"2025-02-24T01:16:22+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fivemor.com\/?p=106166#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fivemor.com\/?p=106166\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/?p=106166#primaryimage\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/new-browsing-behavior-dimensions.jpg\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/new-browsing-behavior-dimensions.jpg\",\"width\":1078,\"height\":492},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fivemor.com\/?p=106166#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fivemor.com\/?bp_activities=1\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Track Browsing Behavior In Google Analytics\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/fivemor.com\/#website\",\"url\":\"https:\/\/fivemor.com\/\",\"name\":\"Som2ny Network\",\"description\":\"Daily Deals\",\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/fivemor.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/fivemor.com\/#organization\",\"name\":\"Som2ny Network\",\"url\":\"https:\/\/fivemor.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"width\":300,\"height\":86,\"caption\":\"Som2ny Network\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"caption\":\"admin\"},\"sameAs\":[\"https:\/\/fivemor.com\"],\"url\":\"https:\/\/fivemor.com\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Track Browsing Behavior In Google Analytics - Som2ny Network","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/fivemor.com\/?p=106166","og_locale":"en_US","og_type":"article","og_title":"Track Browsing Behavior In Google Analytics - Som2ny Network","og_description":"Last updated 22 May 2019: Added expiration of currently open tabs. In this article, Jethro Nederhof of Snowflake Analytics fame and I will introduce you to some pretty neat web browser APIs. The purpose of these APIs is to find out more about how the user navigated to the current page, and what\u2019s going on [&hellip;]","og_url":"https:\/\/fivemor.com\/?p=106166","og_site_name":"Som2ny Network","article_published_time":"2025-02-24T01:16:22+00:00","og_image":[{"width":1078,"height":492,"url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/new-browsing-behavior-dimensions.jpg","type":"image\/jpeg"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"18 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fivemor.com\/?p=106166#article","isPartOf":{"@id":"https:\/\/fivemor.com\/?p=106166"},"author":{"name":"admin","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371"},"headline":"Track Browsing Behavior In Google Analytics","datePublished":"2025-02-24T01:16:22+00:00","mainEntityOfPage":{"@id":"https:\/\/fivemor.com\/?p=106166"},"wordCount":3545,"commentCount":0,"publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"image":{"@id":"https:\/\/fivemor.com\/?p=106166#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/new-browsing-behavior-dimensions.jpg","keywords":["Analytics","Behavior","browsing","Google","Track"],"articleSection":["Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fivemor.com\/?p=106166#respond"]}]},{"@type":"WebPage","@id":"https:\/\/fivemor.com\/?p=106166","url":"https:\/\/fivemor.com\/?p=106166","name":"Track Browsing Behavior In Google Analytics - Som2ny Network","isPartOf":{"@id":"https:\/\/fivemor.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/fivemor.com\/?p=106166#primaryimage"},"image":{"@id":"https:\/\/fivemor.com\/?p=106166#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/new-browsing-behavior-dimensions.jpg","datePublished":"2025-02-24T01:16:22+00:00","breadcrumb":{"@id":"https:\/\/fivemor.com\/?p=106166#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fivemor.com\/?p=106166"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/?p=106166#primaryimage","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/new-browsing-behavior-dimensions.jpg","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/new-browsing-behavior-dimensions.jpg","width":1078,"height":492},{"@type":"BreadcrumbList","@id":"https:\/\/fivemor.com\/?p=106166#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fivemor.com\/?bp_activities=1"},{"@type":"ListItem","position":2,"name":"Track Browsing Behavior In Google Analytics"}]},{"@type":"WebSite","@id":"https:\/\/fivemor.com\/#website","url":"https:\/\/fivemor.com\/","name":"Som2ny Network","description":"Daily Deals","publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/fivemor.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/fivemor.com\/#organization","name":"Som2ny Network","url":"https:\/\/fivemor.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","width":300,"height":86,"caption":"Som2ny Network"},"image":{"@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371","name":"admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","caption":"admin"},"sameAs":["https:\/\/fivemor.com"],"url":"https:\/\/fivemor.com\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/106166","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=106166"}],"version-history":[{"count":0,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/106166\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/media\/106169"}],"wp:attachment":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=106166"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=106166"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=106166"},{"taxonomy":"dealstore","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fdealstore&post=106166"},{"taxonomy":"offerexpiration","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fofferexpiration&post=106166"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}