Thanks to jsegars for the insight that solved the problem. I’d been been putting off reviewing and implementing this for many months, but here’s how I solved it with a custom Tag in Google Tag Manager:
1. Create a GTM Trigger of the type “History Change” that fires on All History Changes.
2. Create a GTM tag with custom HTML, that fires based on the Trigger you set in Step 1.
3. Add a <script> to your Tag that creates a Mutation observer and logs your mutations:
<script>
var mutationObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutations);
});
});
mutationObserver.observe(document.body, {
attributes: false,
characterData: false,
childList: true,
subtree: false,
attributeOldValue: false,
characterDataOldValue: false
});
</script>
4. Publish your updated GTM container.
5. Load a page and open the console. Then click to a new page on your website, and see what mutations take place on the page change in your web app. You need to find a mutation that is (1) consistent across all page changes, and (2) executed after the content on your page loads.
- This latter point is critical, because if you execute the script before the Hubspot CTA element finishes loading, the script isn’t going to have anything to modify. (You could put the script on a timer instead of using a mutation observer, but that’s risky because you don’t know how long each user’s page load would take).
- On my company’s website, I had to test a few options, but the most appropriate mutation was “the removal of the progress bar that visually cues page changes”. This progress bar on our site always has the HTML ID “nprogress” — so with my Mutation Observer in Javascript, I can check for it with mutation.removedNodes[0].id == “nprogress”
6. Update your GTM script to check for the relevant mutation, and when it finds it, to run jsegars’s script and disconnect the mutation observer.
- You can safely disconnect the mutation observer, because your GTM implementation re-inserts it on every history change.
- Your exact implementation logic is going to vary based on what you’re matching against. In my case, the nprogress node is always the only removed node in its mutation, and not every mutation has any removed nodes, so I also check for removedNodes.length > 0 to avoid throwing console errors:
<script>
var mutationObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.removedNodes.length > 0 && mutation.removedNodes[0].id == "nprogress") {
window.hubspot_web_interactives_running = false;
var CTArefresh = document.createElement('script');
CTArefresh.src='https://js.hubspot.com/web-interactives-embed.js';
document.body.appendChild(CTArefresh);
mutationObserver.disconnect();
}
});
});
mutationObserver.observe(document.body, {
attributes: false,
characterData: false,
childList: true,
subtree: false,
attributeOldValue: false,
characterDataOldValue: false
});
</script>
7. Don’t forget to publish your updated GTM container again, and make sure that everything consistently works.