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:
- Add browser history and a unique title for every virtual page.
- 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.
- Login to GTM
- 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_viewevents, I don't want this configuration to send a duplicate. - 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:

- On my SPA, I add this line above the GTM tag in the
<header>:<script> window.dataLayer = window.dataLayer || []; </script> <!-- Google Tag Manager --> - Elsewhere in my code, I manually use that
dataLayerto fire my trigger:dataLayer.push({ 'event': 'Page Loaded', 'page_url': ..., 'page_title': ..., }); - Back in GTM, I create data layer variables for those two
page_*fields:
- I create a
page_viewtag that reacts to this trigger and uses those variables.
- I click the preview button to see how it's working.

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:
The tag looks like this:
And the Google Analytics Hit looks like this (on the Tag Assistant page):
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.


