Skip to content

What is the canonical way to manually fire page_view in Google Tag Manager and Google Analytics 4 (GA4)?

My website is a single page app (SPA) that never changes browser history or the page title. Therefore, it’s my understanding that I have two options if I want to capture page_view events:

  1. Add browser history and a unique title for every virtual page.
  2. Manually fire a page_view in SPA code.

I’m going with option 2.

I’ve read numerous articles on how to do this, and they tend to converge on similar advice, but they’re always a little different from each other. This is probably because most were written soon after GA4’s release. After trying most of the steps in these articles, I’m not convinced I’ve set up virtual pageviews correctly. Unfortunately, the official documentation provides little guidance:

Alternatively, you can always explicitly set page parameters when sending a page_view event to ensure accuracy and clarity.

When it comes to the official documentation, I’ve only been able to find guidance on doing this with gtag.js, but I’m not using that. I’m using Google Tag Manager (GTM). Here are the steps I’ve taken and my rationale for taking them (besides an article telling me to do so). After this list, I’ll explain why I think it’s not working correctly.

  1. Login to GTM
  2. Create a Google Analytics GA4 Configuration but uncheck “Send a page view event when this configuration loads”. I do this because, if I’m going to manually send page_view events, I don’t want this configuration to send a duplicate.
  3. Create a custom event trigger named “Page Loaded”. This allows me to control when I fire this trigger. There’s nothing special about it yet. Here’s how it looks: enter image description here
  4. On my SPA, I add this line above the GTM tag in the <header>:
      window.dataLayer = window.dataLayer || [];
    <!-- Google Tag Manager -->
  5. Elsewhere in my code, I manually use that dataLayer to fire my trigger:
      'event': 'Page Loaded',
      'page_url': ...,
      'page_title': ...,
  6. Back in GTM, I create data layer variables for those two page_* fields: enter image description here
  7. I create a page_view tag that reacts to this trigger and uses those variables. enter image description here
  8. I click the preview button to see how it’s working. enter image description here

Now, as far as I can tell, this tag is firing at the right time and passing all the right information. But here’s why I think there’s something wrong:

If I view my other tag, the google analytics hit information has Page Location and Page Title values of the actual page, not the virtual page (aka the custom event I created). This other tag’s trigger looks like this:

enter image description here

The tag looks like this:

enter image description here

And the Google Analytics Hit looks like this (on the Tag Assistant page):

enter image description here

Those red arrows have the value of the actual page.

Is there some step/configuration I’m missing? I would expect all the tags to use the Page Title of my page_view tag.



Right, that’s cuz either the fields aren’t being inherited from the settings variable (I have noticed that behavior in GA4 before) or the values of your DL variables are not set at that point (which is unlikely).

An obvious fix for it would be just adding your fields to the click tag and be done with it.

The way I do GA4 tags is by making one single tag. For everything. All its content are variables, including the name of the event. And all the logic for it is either in regex lookup tables or in CJS. Or in both: CJS that uses rLUTs.

Now it may seem complicated and overengineered, but now the size of your GA4 set up is small (remember: the size of the container is limited), it’s easy to manage if you love your JS (all logic is in one place) and you don’t need to iterate through all your dimensions every time you need a new event to fire.

So I basically treat a GA4 event tag as a config variable. In your case, you can even merge the pageview and the click events into one.

Also, not having history changes is a really poor practice and I would switch the site engine completely. It will cause dramatic issues in other places, like SEO.