top of page
Search

Debugging sGTM Preview Mode in a Google Tag Gateway World

  • 1 day ago
  • 5 min read

Updated: 5 hours ago

If you have ever tried to debug server-side GTM behind a Google Tag Gateway setup, you will have felt the quiet pain of a preview window that refuses to light up. The hits are going through, the data looks fine, and yet Preview Mode sits there in silence. 

In a previous post we discussed GTG and sGTM working together and the topic of preview mode was mentioned in passing. This time, we’ll unpick what, how, why, and what to do about it when it goes out of true.


Client side vs server side: two different beasts

Your regular run-of-the-mill client side preview is obviously not same-origin à la GTG, but it sure gets away with a lot. When you click Preview in the web container, Tag Assistant opens your site, the browser fetches gtm.js from googletagmanager.com, and Google returns the preview version of the container. The point is: the web container has its own machinery to bridge origins.


Server side preview is structured differently. When you open the Preview panel for your server container, a first-party cookie is set on your sGTM subdomain (for example sgtm.example.com). A request will show up on the timeline if it either carries that cookie or carries an HTTP header: X-Gtm-Server-Preview. The cookie route only works when the browser sends its request straight to the sGTM subdomain. The header route works from anywhere, which is the escape hatch we will need shortly. Miss both, and you get silence.


Enter Google Tag Gateway on Cloudflare

In a Tag Gateway setup, the client does not talk to your sGTM subdomain directly. It talks to a path on your own domain (for example https://dugadigital.com/duganalytics/g/collect), and a Cloudflare Worker re-writes that request to your sGTM host (for example https://sgtm.dugadigital.com/g/collect).

Under the bonnet/hood the Worker has to do three things for each request:

  • Strip the /dugaddingwell path prefix and keep everything after it, such as /g/collect or /gtm.js.

  • Set the Host header to sgtm.example.com so the server container recognises the hostname.

  • Forward the request on to the sGTM origin.


You also need a matching config change on the server container so it knows which URLs it is willing to serve.


On the happy path this works a treat. You will see responses like:

event: message

data: {"response":{"status_code":200,"body":""}}


Good for data collection. But fire up Preview Mode and the timeline stays empty.


Why Preview breaks across a subdomain boundary

Here is the catch. The preview cookie lives on sgtm.dugadigital.com, but in a Tag Gateway setup the browser never sends requests to that origin. It sends them to dugadigital.com/duganalytics/..., and the browser has no reason to attach a sgtm.dugadigital.com cookie to a request for a different origin. 


The Cloudflare Worker then rewrites the Host header and forwards the request upstream, but by that point the cookie was never in the request to begin with. Requests arrive at the server container, are processed, and come back 200 OK. They are just not tagged for the preview session, so they never make it onto the timeline.


Hits are successful. Preview is silent. When proxying sGTM through a Cloudflare Worker, the biggest hurdle is not the data, it is the authorisation.


The fix: scrape the preview ID, inject the header

Google provides the X-Gtm-Server-Preview HTTP header as an "escape hatch" for environments where cookies cannot be reliably passed.


The trick is to get the preview ID into X-Gtm-Server-Preview before the Worker forwards the request. Simo Ahava’s Tip 132 has the shape of the solution: grab the preview ID attached by the client side container (either as gtm_preview on the query string or embedded in the Referer) and translate it into a proper header.

Here is the core of the Worker logic:

if (!previewId) {

  const referer = request.headers.get('Referer');

  if (referer) {

    try {

      const refererUrl = new URL(referer);

      previewId = refererUrl.searchParams.get('gtm_preview');

      if (previewId) console.log(`> Found ID in Referer: ${previewId.substring(0, 10)}...`);

    } catch (e) {}

  }

}


if (previewId) {

  newHeaders.set('X-Gtm-Server-Preview', previewId);

  targetUrl.searchParams.delete('gtm_preview');

  targetUrl.searchParams.delete('ep.gtm_preview');

}


Two things worth calling out. First, checking the Referer is important. Modern browsers do not always preserve query parameters on every hit, but they do tend to preserve the Referer, which gives you a second bite at the cherry. Second, once the ID has been promoted into a header, you want it gone from the URL and gone from the event parameters. The last two delete lines strip gtm_preview and ep.gtm_preview from the outgoing request, so you do not pollute your collected data with a debug flag that now lives in a header.


The loss of query params gotcha, and how ModHeader saves you

If your site doesn’t preserve querystrings or the gtm_preview value is lost somehow, there is one more trap. GTM normally appends debug query parameters to the initial page URL in Preview Mode. But SPAs, for example, might handle their own client-side routing. By the time you click through to a new "page", those query parameters can be rewritten or stripped by the router, the Referer no longer carries them, and your requests go out naked. Nothing for the Worker to scrape, no header injected, Preview silent again.

The pragmatic workaround is a browser extension such as ModHeader. Copy the preview ID from the sGTM Preview window, add it as a fixed X-Gtm-Server-Preview header in ModHeader, and every request the browser sends from then on carries it, route changes be damned. The Cloudflare Worker passes it through, and Preview Mode lights up as expected. This is a debugging aid, not a production fix. Turn ModHeader off when you are done.


Wrapping up

Debugging sGTM through a Google Tag Gateway comes down to one realisation: client side preview rides on cookies, server side preview rides on a header, and the Gateway sits across a subdomain boundary that makes both harder.


By scraping the preview ID from the Referer, injecting it as X-Gtm-Server-Preview inside the Cloudflare Worker, and reaching for ModHeader when your SPA eats the query parameters, you keep the debug session connected, keep your URL parameters clean, and keep your data collection honest.


Credit where it is due: thanks to Justus Hämäläinen for the localhost sGTM project featured on Simo Ahava’s blog, and to Simo himself for the tip that points the way on the header injection pattern.


UPDATE

Quite correctly, we received comments on the LinkedIn post to clarify that when you're running GTG and sGTM on your own GCP infra, you can avoid the issues with the subdomain by configuring sGTM to use a split path architecture, and stay first party, same site, same origin. The CDN or Load Balancer handles the routing and there's no drama with headers. If you're using a SaaS sGTM provider and using a subdomain, this article will still be of use to you.


References

 
 
 

Recent Posts

See All
H1 Google Analytics Roadmap inside track

Last week we attended the Google Analytics H1 roadmap webinar and came away with a clear picture of where things are heading. We cannot share the specifics (NDA firmly in place) but we can tell you wh

 
 
 

Comments


Google_GMP_Certified_Badge_Final_Med (1).png
  • Facebook
  • LinkedIn

CONTACT US

Lime Tree Work Shop, Lime Tree Walk, Sevenoaks, Kent, TN13 1YH

info@dugadigital.com

Registered in England and Wales no. 13177452.
VAT Registration no. 397 6168 39.

bottom of page