{"id":126,"date":"2019-10-13T20:01:36","date_gmt":"2019-10-13T19:01:36","guid":{"rendered":"https:\/\/organicdigital.co\/blog\/?p=126"},"modified":"2025-07-29T23:44:35","modified_gmt":"2025-07-29T22:44:35","slug":"how-to-implement-meta-data-on-hashbang-urls-using-gtm","status":"publish","type":"post","link":"https:\/\/daveashworth.co\/blog\/how-to-implement-meta-data-on-hashbang-urls-using-gtm\/","title":{"rendered":"How To: Implement Meta Data on Hashbang URLs using GTM"},"content":{"rendered":"\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_82_2 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/daveashworth.co\/blog\/how-to-implement-meta-data-on-hashbang-urls-using-gtm\/#The_Story_Behind_This_Case_Study\" >The Story Behind This Case Study<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/daveashworth.co\/blog\/how-to-implement-meta-data-on-hashbang-urls-using-gtm\/#What_is_a_Hashbang_URL\" >What is a Hashbang URL?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/daveashworth.co\/blog\/how-to-implement-meta-data-on-hashbang-urls-using-gtm\/#How_Do_Hashbang_URLs_affect_SEO\" >How Do Hashbang URLs affect SEO?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/daveashworth.co\/blog\/how-to-implement-meta-data-on-hashbang-urls-using-gtm\/#How_To_Implement_Meta_Data_Using_Google_Tag_Manager\" >How To Implement Meta Data Using Google Tag Manager<\/a><\/li><\/ul><\/nav><\/div>\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"The_Story_Behind_This_Case_Study\"><\/span>The Story Behind This Case Study<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>I recently did some <a href=\"https:\/\/daveashworth.co\/skills\/wordpress-seo-consultant\/\">WordPress SEO Consultancy<\/a> for a website that was configured to pull in landing page content across a number of locations from a 3<sup>rd<\/sup> party source using AJAX.\u00a0\u00a0 There were two issues:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>The URLs included a hashbang<\/li>\n\n\n\n<li>The content was injected into the page template,\nwhich meant the title tag, meta description and canonical tag all pointed to\nthe primary URL without the hash<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"What_is_a_Hashbang_URL\"><\/span>What is a Hashbang URL?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>A hashbang URL effectively contains #! \u2013 everything to the\nleft of it is a standard URL, everything to the right is used to load in\ndynamic content.&nbsp; <\/p>\n\n\n\n<p>For example:<\/p>\n\n\n\n<p>https:\/\/domain.com\/services-near-you\/#!\/location-a<br>https:\/\/domain.com\/services-near-you \/#!\/location-b<br>https:\/\/domain.com\/services-near-you \/#!\/location-c<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"How_Do_Hashbang_URLs_affect_SEO\"><\/span>How Do Hashbang URLs affect SEO?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>For a few years now Google has been able to crawl and index\nsuch URLs with no issues and recognises them as unique pages.<\/p>\n\n\n\n<p>The issue in this instance was that whilst Google was\ncrawling and indexing each of the locations with unique in page content\ngenerated by AJAX, but because of how the website was configured, the meta data\nin terms of page title, canonical and meta description all resolved to that of\nthe parent URL:<\/p>\n\n\n\n<p>https:\/\/domain.com\/services-near-you\/<\/p>\n\n\n\n<p>As there were over 30 of these location pages, there were 30+ pages targeting different locations with the same non-targeted meta data. <\/p>\n\n\n\n<p>E.g. every page had the following:<\/p>\n\n\n\n<p>Title:&nbsp; Our Services Near You | brand<br>Meta Description: Find out more about our services at each of our locations<br>Canonical Tag: https:\/\/domain.com\/services-near-you\/<\/p>\n\n\n\n<p>Despite this, because of the in page content that had specific location data\/content, they generated reasonable levels of traffic, but then, their performance dropped following the <a href=\"https:\/\/www.gsqi.com\/marketing-blog\/march-12-2019-google-core-algorithm-update\/\" target=\"_blank\" rel=\"noopener\">March 12<sup>th<\/sup> Google Update<\/a>:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" width=\"669\" height=\"170\" src=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/sevices-traffic.png\" alt=\"Traffic drop\" class=\"wp-image-127\" srcset=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/sevices-traffic.png 669w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/sevices-traffic-150x38.png 150w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/sevices-traffic-300x76.png 300w\" sizes=\"(max-width: 669px) 100vw, 669px\" \/><\/figure>\n\n\n\n<p>These pages weren\u2019t the only ones affected by the drop, and there is work to be done in general around site content and EAT (this is a site that\u2019s actually within the Medic niche), but, it was clear that the services near you pages were affected because of this issue:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" width=\"781\" height=\"802\" src=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/sevices-impressions.png\" alt=\"Impressions Drop\" class=\"wp-image-128\" srcset=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/sevices-impressions.png 781w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/sevices-impressions-146x150.png 146w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/sevices-impressions-292x300.png 292w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/sevices-impressions-768x789.png 768w\" sizes=\"(max-width: 781px) 100vw, 781px\" \/><\/figure>\n\n\n\n<p>It became apparent that there was no development resource to make the fundamental changes to site functionality so that each page could generate unique meta data, so, as has been the case so many times before, I turned to <a href=\"https:\/\/daveashworth.co\/skills\/google-tag-manager-consultant\/\">Google Tag Manager to resolve the issues<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"How_To_Implement_Meta_Data_Using_Google_Tag_Manager\"><\/span><strong>How To Implement Meta Data Using Google Tag Manager<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>The method I chose to use was some Javascript via a custom HTML tag as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;script&gt;\n&lt;!-- remove the existing elements --&gt;\njQuery('meta&#91;name=\"description\"]').remove();\njQuery('title').remove();\njQuery('link&#91;rel=\"canonical\"]').remove();\n\n&lt;!-- create and append a new title tag --&gt;\nvar new_tt = document.createElement('title');\nnew_tt.text = '\"Location Services\" | \"Brand\"';\njQuery('head').append(new_tt);\n\n&lt;!-- create and append a new canonical tag --&gt;\nvar new_can = document.createElement('link');\nnew_can.rel = 'canonical';\nnew_can.href = 'https:\/\/domain.com\/services-near-you\/#!\/location-a';\njQuery('head').append(new_can);\n\n&lt;!-- create and append a new meta description --&gt;\nvar new_md = document.createElement('meta');\nnew_md.name = 'description';\nnew_md.content = 'Click through for contact details for our \"location\" branch and to book appointments.';\njQuery('head').append(new_md); \n&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<p>It\u2019s fairly straightforward \u2013 you remove the existing elements, then create and populate the new ones.<\/p>\n\n\n\n<p>The stumbling block came about when trying to implement this\nat URL level.&nbsp; For example, you would normally\nset up a trigger as follows:<\/p>\n\n\n\n<p>https:\/\/domain.com\/services-near-you\/#!\/location-a<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" width=\"890\" height=\"176\" src=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/trigger-1.png\" alt=\"trigger\" class=\"wp-image-130\" srcset=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/trigger-1.png 890w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/trigger-1-150x30.png 150w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/trigger-1-300x59.png 300w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/trigger-1-768x152.png 768w\" sizes=\"(max-width: 890px) 100vw, 890px\" \/><\/figure>\n\n\n\n<p>However, hashbang URLs don\u2019t work in the same way normal URLs do when setting up a trigger as anything after the # is effectively ignored and not part of the full URL, so instead you need to create a custom Javascript variable that pulls in the full URL with everything to the right of the # included, this is done with the following script:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function() {\n  return window.location.pathname + window.location.search + window.location.hash;\n}<\/code><\/pre>\n\n\n\n<p>You then create a custom variable of type \u201cJavascript Variable\u201d:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" width=\"889\" height=\"661\" src=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/javascript-variable-1.png\" alt=\"Javascript Variable\" class=\"wp-image-132\" srcset=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/javascript-variable-1.png 889w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/javascript-variable-1-150x112.png 150w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/javascript-variable-1-300x223.png 300w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/javascript-variable-1-768x571.png 768w\" sizes=\"(max-width: 889px) 100vw, 889px\" \/><\/figure>\n\n\n\n<p>Then set that up as a trigger to fire when the variable contains your desired string:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" width=\"696\" height=\"164\" src=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/hashbang-trigger.png\" alt=\"hashbang trigger\" class=\"wp-image-133\" srcset=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/hashbang-trigger.png 696w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/hashbang-trigger-150x35.png 150w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/hashbang-trigger-300x71.png 300w\" sizes=\"(max-width: 696px) 100vw, 696px\" \/><\/figure>\n\n\n\n<p>The good news for us was that this all worked, and Google\nwill crawl, parse and index the modified content that is generated and added to\neach page in this way.<\/p>\n\n\n\n<p>The even better news was that now each page had its own unique meta data, following the latest <a href=\"https:\/\/www.seroundtable.com\/google-september-2019-core-update-impact-28283.html\" target=\"_blank\" rel=\"noopener\">Google Update on September 24<sup>th<\/sup><\/a>, the location pages started to see an improvement in visibility:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" width=\"798\" height=\"831\" src=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/impressions-improvement.png\" alt=\"Impressions Increase\" class=\"wp-image-134\" srcset=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/impressions-improvement.png 798w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/impressions-improvement-144x150.png 144w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/impressions-improvement-288x300.png 288w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/impressions-improvement-768x800.png 768w\" sizes=\"(max-width: 798px) 100vw, 798px\" \/><\/figure>\n\n\n\n<p>Which has led to a 20% increase in traffic to these pages since:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" width=\"837\" height=\"775\" src=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/traffic-improvment.png\" alt=\"Traffic Boost\" class=\"wp-image-135\" srcset=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/traffic-improvment.png 837w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/traffic-improvment-150x139.png 150w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/traffic-improvment-300x278.png 300w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2019\/10\/traffic-improvment-768x711.png 768w\" sizes=\"(max-width: 837px) 100vw, 837px\" \/><\/figure>\n\n\n\n<p>Which was nice. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Some websites are configured in a way that serves duplicate content and meta data which you cannot change via the CMS, such as AJAX generated content on hashbang URLs.  However, Google Tag Manager allows you to amend these so the changes will get indexed.<\/p>\n","protected":false},"author":1,"featured_media":136,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[],"class_list":["post-126","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tag-manager"],"_links":{"self":[{"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/posts\/126","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/comments?post=126"}],"version-history":[{"count":1,"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/posts\/126\/revisions"}],"predecessor-version":[{"id":1730,"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/posts\/126\/revisions\/1730"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/media\/136"}],"wp:attachment":[{"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/media?parent=126"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/categories?post=126"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/tags?post=126"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}