Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Analytics GTAG script installed twice #2628

Closed
emac3 opened this issue Feb 13, 2020 · 36 comments
Closed

Analytics GTAG script installed twice #2628

emac3 opened this issue Feb 13, 2020 · 36 comments

Comments

@emac3
Copy link

emac3 commented Feb 13, 2020

Describe your environment

  • Operating System version: macOS Mojave 0.14.6 (18G3020)
  • Browser version: Chrome 80.0.3987.100 (Official Build) (64-bit)
  • Firebase SDK version: firebase@7.8.1
  • Firebase Product: analytics

[REQUIRED] Describe the problem

When I load Firebase Web with analytics on Chrome with the
Google Analytics Debugger extension enabled, I get the following warning from the console: "GTAG script is installed twice."

Steps to reproduce:

Load a page with Firebase Analytics.

Relevant HTML head scripts:

<head>
  <script type="text/javascript" async="" src="http://www.googletagmanager.com/gtag/js?id=G-XXX&amp;l=dataLayer&amp;cx=c"></script>
  <script src="https://www.googletagmanager.com/gtag/js?l=dataLayer" async=""></script>
</head>

Google Analytics Debugger extension logs:

  __ _| |_ __ _  __ _   (_)___
 / _` | __/ _` |/ _` |  | / __|
| (_| | || (_| | (_| |_ | \__ \
 \__, |\__\__,_|\__, (_)/ |___/
 |___/          |___/ |__/


11:35:18.946 Processing commands (1)
11:35:18.946 Processing GTAG command: ["js", Thu Feb 13 2020 11:35:18 GMT+0100 (Central European Standard Time)]
11:35:18.948 No tags fired for event: gtm.js
11:35:18.949 Processing commands (1)
11:35:18.949 Processing GTAG command: ["config", "G-XXX", {firebase_id: "XXX", origin: "firebase", update: true}]
11:35:18.950 GTAG Command: "config", target: "G-XXX", configuration: {firebase_id: "XXX", origin: "firebase", update: true}
11:35:18.952 Tag fired: {function: "__get", vtp_trackingId: "G-XXX", vtp_isAutoTag: true}
11:35:18.955 Processing commands (0)
11:35:18.956 Processing commands (1)
11:35:18.956 Processing data layer push: {event: "gtm.dom"}
11:35:18.956 No tags fired for event: gtm.dom
11:35:18.957 Processing commands (1)
11:35:18.957 Processing data layer push: {event: "gtm.load"}
11:35:18.957 No tags fired for event: gtm.load
11:35:19.010        _                 _
  __ _| |_ __ _  __ _   (_)___
 / _` | __/ _` |/ _` |  | / __|
| (_| | || (_| | (_| |_ | \__ \
 \__, |\__\__,_|\__, (_)/ |___/
 |___/          |___/ |__/


11:35:19.014 Processing commands (4)
11:35:19.014 Processing GTAG command: ["js", Thu Feb 13 2020 11:35:18 GMT+0100 (Central European Standard Time)]
11:35:19.016 gsi could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.016 ftld lookup could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.017 icvr could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.019 gsi could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.019 ftld lookup could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.019 icvr could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.019 Tag fired: {function: "__gct", instance_name: "8", vtp_trackingId: "G-XXX", vtp_adFeatures: false, vtp_sessionDuration: 0, vtp_googleSignals: ["macro", 1], vtp_foreignTld: ["macro", 2], vtp_restrictDomain: ["macro", 3], vtp_eventSettings: ["map", "ecommerce_purchase", ["map", "blacklisted", false, "conversion", true], "purchase", ["map", "blacklisted", false, "conversion", true]], tag_id: 7}
11:35:19.023 Loaded existing client id: XXX
11:35:19.026 Event would be batched, but batching is disabled in debug mode.
11:35:19.027 Sending event "page_view" to undefined
11:35:19.027 Request parameters:
11:35:19.028 v: 2
11:35:19.028 tid: G-XXX
11:35:19.028 gtm: 2oe250
11:35:19.029 _p: 826320161
11:35:19.029 sr: 1920x1080
11:35:19.029 _dbg: 1
11:35:19.030 ul: en-us
11:35:19.030 _fid: XXX
11:35:19.030 cid: XXX
11:35:19.031 Event parameters:
11:35:19.031 en: page_view
11:35:19.032 ep.origin: firebase
11:35:19.032 Shared parameters:
11:35:19.033 dl: XXX
11:35:19.033 dr: XXX
11:35:19.033 dt: XXX
11:35:19.034 sid: XXX
11:35:19.034 sct: XXX
11:35:19.034 seg: XXX
11:35:19.035 Sending request: https://www.google-analytics.com/g/collect?v=2&tid=G-XXX&...
11:35:19.036 gsi could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.037 ftld lookup could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.037 icvr could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.038 Processing GTAG command: ["config", "G-XXX", {firebase_id: "XXX", origin: "firebase", update: true}]
11:35:19.038 GTAG Command: "config", target: "G-XXX", configuration: {firebase_id: "XXX", origin: "firebase", update: true}
11:35:19.038 GTAG script is installed twice.
11:35:19.039 gsi could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.039 ftld lookup could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.039 icvr could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.040 No tags fired for event: gtag.config
11:35:19.040 gsi could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.040 ftld lookup could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.040 icvr could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.040 Processing data layer push: {event: "gtm.dom", gtm.uniqueEventId: 2}
11:35:19.041 gsi could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.041 ftld lookup could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.041 icvr could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.041 No tags fired for event: gtm.dom
11:35:19.042 gsi could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.042 ftld lookup could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.042 icvr could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.042 Processing data layer push: {event: "gtm.load", gtm.uniqueEventId: 3}
11:35:19.042 gsi could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.042 ftld lookup could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.043 icvr could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.043 No tags fired for event: gtm.load
11:35:19.043 gsi could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.043 ftld lookup could not be evaluated client side. This container may have different behavior outside debug mode.
11:35:19.043 icvr could not be evaluated client side. This container may have different behavior outside debug mode.

Relevant Code:

import * as firebase from "firebase/app";
import "firebase/analytics";
import React, { useState } from "react";
import { AnalyticsContext } from "./analyticsUtils";

const defaultConfig = {};

interface Props {
  config?: { [key: string]: any };
}

const AnalyticsProvider: React.FunctionComponent<Readonly<Props>> = function(
  props
) {
  const analytics = firebase
    .initializeApp({
      ...defaultConfig,
      ...props.config
    })
    .analytics();

  const [value] = useState({
    logEvent: analytics.logEvent
  });

  return (
    <AnalyticsContext.Provider value={value}>
      {props.children}
    </AnalyticsContext.Provider>
  );
};

export default AnalyticsProvider;
@google-oss-bot
Copy link
Contributor

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

@rommelpe
Copy link

Sorry for the late response here, @emac3. Per this reference, it seems that you have to remove the Measurement ID from the googletagmanager script. When you connect Firebase and Gtag, the Gtag SDK needs to have the Measurement ID removed, because Firebase provides its own to connect with the SDK.

@hsubox76
Copy link
Contributor

One question, are you simultaneously using Firebase Analytics (firebase.analytics().logEvent etc) and direct calls to gtag.js (gtag('event')) on this page, or is this page only using analytics through Firebase Analytics? If it's the latter (Firebase Analytics only) you can get rid of those script tags entirely. If the former, see the documentation @rommelpe linked above.

@emac3
Copy link
Author

emac3 commented Feb 21, 2020

@rommelpe @hsubox76 thanks for your replies, fact is those two script tags are added by Firebase at runtime. I'm only using analytics through Firebase Analytics. I didn't install gtag by myself. See my code snippet above.

@hsubox76
Copy link
Contributor

I can't seem to reproduce this with that code snippet.

I filled in my own project config, I substituted const AnalyticsContext = React.createContext({}); for the missing AnalyticsContext, and I rendered AnalyticsProvider as a child of a top level App component, with no child elements except some text. I also tried rendering 2 AnalyticsProviders under App, giving them 2 different configs through props. I did get an error (Firebase App named '[DEFAULT]' already exists (app/duplicate-app).) for calling initializeApp twice but that was fixed by allowing me to give a different name prop to each AnalyticsProvider and using it in initializeApp.

Is there any way you can provide a full minimal repro that includes how you call the AnalyticsProvider component (if it's multiple times, please include that) and what AnalyticsContext looks like? It doesn't have to be your whole repo, just whatever minimal part I can run to reproduce the error on my own machine.

@emac3
Copy link
Author

emac3 commented Feb 24, 2020

here it is @hsubox76, you just need to pass correct config to AnalyticsProvider in App.js and if you enable Google Analytics Debugger Chrome extension you should see the warning:

"GTAG script is installed twice."

https://codesandbox.io/s/firebase-analytics-gtag-warning-ngtvx?fontsize=14&hidenavigation=1&theme=dark

@hsubox76
Copy link
Contributor

Thank you, I am able to reproduce it. I believe that this warning comes from the unusual way that Firebase Analytics has to wrap gtag.js, which must be downloaded by script tag injection and cannot be bundled at runtime, and additionally requires 2 download phases to avoid triggering a premature page_view event sent before the client has set up its Firebase instance data.

Unfortunately it will result in that warning for now, but hopefully it does not affect actual functionality. Have you observed any bugs in functionality?

@emac3
Copy link
Author

emac3 commented Feb 25, 2020

ok, thanks for the explanation, I'm still setting up the project, but I have not observed any strange behaviour so far, apart from the console warning, I will let you know otherwise.

@tzar
Copy link

tzar commented Mar 9, 2020

I'm having this issue as well, it's resulting in mixed content warnings on my site as one of the added gtag scripts is served over http

@shotasenga
Copy link

shotasenga commented Apr 1, 2020

I have the same issue with my project. And it seems that it sends all events twice and reported as doubled on the stream view. Is there any way to avoid this? Even a temporary solution would be appreciated.


(Edited 2020/04/01 12:47:00 PT)

This is caused by my code. It was calling Firebase.Analytics and gtag at the same time. So please discard this message.

@simone-gross
Copy link

Anything new about that? I am having the issue as well. I am calling only firebase.analytics() and log some custom events to define a funnel. But it seams like the events are not reported reliably. Even if they are logged to be sent correctly to Analytics by the GA Debug Plugin, sometimes they don't or only some events show up at the Debug View or the Realtime Panel of Analytics. It appears randomly. Anybody with same behavior or any ideas how to fix that?

@hsubox76
Copy link
Contributor

hsubox76 commented Apr 9, 2020

The bug, as I reproduced it locally, did not cause any functionality problems. Earlier this week I did have some trouble seeing Analytics events reported in one of my personal projects over the last 24-48 hours, there may have been a backend service issue, and there periodically can be. For what it's worth, when I checked two days later, the missing data had been populated.

If you are still seeing problems with events, can you

  1. check this issue first, which deals with analytics reporting problems more similar to what you are describing: Firebase Analytics doesn't work for Web app #2600
  2. If that seems like the same issue, can you provide, in a comment in that issue, details about your usage and hopefully a minimal reproduction of the problem?
  3. if that seems different than your situation, can you start a new issue with details about your usage and hopefully a minimal reproduction of the problem?

As for the issue described in #1, can you especially look at this comment: #2600 (comment) particularly checking for that POST call to the collect? endpoint? If that call is successful, the data should be being sent to the backend, and the problem may be on that side.

@sceee
Copy link

sceee commented Apr 15, 2020

I can confirm I am having the same issue, this is how it looks like in Chrome DevTools:
image

I also have the GA Debug Plugin report that GTAG script is installed twice.

My config is just:

firebase.initializeApp(firebaseConfig)

and this also only gets called once.
I'm using firebase npm package 7.14.0

@hsubox76
Copy link
Contributor

As mentioned above, this warning message is ugly but does not seem to be causing any functionality issues. If it is causing any functionality issues that you've noticed (not reporting data, reporting data incorrectly, or reporting each event twice), please see my comment 2 posts up for what action to take next.

@sceee
Copy link

sceee commented May 18, 2020

@hsubox76 please have a look at GoogleChromeLabs/bubblewrap#173, this is unfortunately preventing PWAs from getting the appropriate lighthouse scoring causing further issues (e.g. the PWA failing the lighthouse PWA check which causes confusion whether the PWA is ready for use with bubblewrap).

@sceee
Copy link

sceee commented May 23, 2020

I further tried to investigate this issue as the additional http call to http://www.googletagmanager.com/gtag/js is causing further problems now. Since other tools/projects rely on the appropriate lighthouse scoring, this call destroys the ability to use such.

I could unfortunately not trace it further down. I tried changing the import order of the imports

import firebase from 'firebase/app'
import 'firebase/analytics'

but it did also not help (side note: it is bundled with webpack afterwards).
So I - for now until this is fixed in the Firebase or gtag library - decided to work around this issue by adding the CSP

<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

This instructs user agents to upgrade insecure requests - which the one we are talking about here is - automatically to secure ones.
So there are still 2 requests but at least the one is not http anymore.
It's definitely not optimal as it might not work for everybody and could cause other issues but if anyone else is being stuck with this bug, you could also try if this works as a temporary workaround for you.

@JFGHT
Copy link

JFGHT commented Jun 3, 2020

I am having the same problem but it includes functionality issues. In my case, the request is sent but I cannot see anything in the DebugView.
From what I see here in the logs, the event gets executed twice, in which one of them it says Sending event "notification_received" to undefined.
image

If it helps tracking this down:

  • I am switching from injecting Analytics directly via Vue-Analytics to the Firebase approach.
  • Before activating Firebase Analytics, my Firebase project was not showing measurementId in the config section, but now it does. It also changed the apiKey.
  • I've just integrated Firebase Analytics with Google Analytics.

Edit: I just found out that it's working if I use the OLD apiKey, not the one that is being shown in the project settings / config.

@hsubox76 any idea on why could be this happening?

@sceee
Copy link

sceee commented Jul 9, 2020

@JFGHT which old apiKey do you mean and what do you mean by it is working? Is the duplicate initialization gone when using that old apiKey?

@hsubox76 unfortunately this issue is still a rather big problem as it seems - confirmed via DebugView - to skip some pageviews as well as remove/reset the source for users when navigating on the website to another page (same domain).
And all that even though the browser seems to correctly send events to the collect endpoint. But, its most of the time sending two events to the collect endpoint (1 ...&en=page_view, one ...&en=user_engagement) where the user_engagement request is pending for a long long time (I think it never completes):
image
First is the user_engagement call, second the page_view.

Is there anything we can do to prevent fix this issue as it makes using FB analytics really untrustworthy?

@johndunderhill
Copy link

I'm seeing this also, in a very clean setup. I'm seeing pending (unresolved) requests. Any timeline on getting to the bottom of this? @hsubox76

@hsubox76
Copy link
Contributor

I think there may be a number of separate issues here and I am trying to sort out which are related.

As far as the apiKey/measurementId issue, there may have been some project config bug. We just released a big change to analytics where measurementId is no longer required in the config and is dynamically fetched based on your config's apiKey and appId so that situation may have changed (hopefully for the better, possibly for worse). If you're still having trouble, can you contact Firebase Support, where you can privately give them your specific project config details and see if they can track it down.

As for the Lighthouse http/https issue, I think the http call is made inside the gtag code which we unfortunately do not have any control over. I'll look into this further.

As for the pending request, I think it's not related to Firebase and may possibly be intended functionality. I made an html page with no Firebase, just using gtag directly (https://developers.google.com/analytics/devguides/collection/gtagjs) and inserting my Firebase app's measurementId, and got the same requests. The page view event showed up fine in the dashboard either way.

If you see a problem using Firebase Analytics that does not happen when using plain gtag.js in an html page with the same measurementId, please let me know. In general this is a good way to narrow down which problems are Firebase specific.

@tzar
Copy link

tzar commented Sep 12, 2020

I'm not sure if it's a workaround or the actual cause, but in my case I realised that the duplicate gtag loads happened during the prerender phase (vue + prerender-spa-plugin). I added post-processing rules to remove them and now just a single gtag injection happens at runtime

Edit: never mind, I still see the Sending event "page_view" to undefined and double gtag.js load in console even with the pre-rendered loads removed. But the above did resolve my http/https mixed content errors at least

@johndunderhill
Copy link

@hsubox76, thank you for your detailed reply. To be clear, my concern is the bahavior of Firebase Analytics when initialized. @tzar, this has nothing to do with frameworks. I can re-create the problem with a plain HTML page. I have not found anything that will stop this.

After importing Firebase from the CDN (version 7.19.1), and initializing Firebase, I initialize Analytics. At that point, the Google library injects two scripts into the head of my page, one of which appears to be superflous. In addition, it makes two POST calls, one of which never resolves, leading the browser and performance monitoring tools like Lighthouse to think the page is still loading.

Here are the two scripts injected into the head:

<script type="text/javascript" async="" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX&amp;l=dataLayer&amp;cx=c"></script>
<script src="https://www.googletagmanager.com/gtag/js?l=dataLayer" async=""></script>

This call stays pending forever:

https://www.google-analytics.com/g/collect?v=2&tid=G-XXXXXXXXXX&gtm=2oe920&_p=1584065445&sr=1920x1080&ul=en-us&_fid=cGypl-WCHc8S3LcQnzTKci&cid=1765209537.1599819643&_s=2&dl=https%3A%2F%2Fcostvine.app%2F&dr=&dt=Costvine%20%E2%80%A2%20Budgets%20made%20easy&sid=1600059793&sct=7&seg=0&en=user_engagement&_et=2966&ep.origin=firebase

This call returns quickly with a 204:

https://www.google-analytics.com/g/collect?v=2&tid=G-XXXXXXXXXX&gtm=2oe920&_p=1208409921&sr=1920x1080&ul=en-us&_fid=cGypl-WCHc8S3LcQnzTKci&cid=1765209537.1599819643&_s=1&dl=https%3A%2F%2Fcostvine.app%2F&dr=&dt=Costvine%20%E2%80%A2%20Budgets%20made%20easy&sid=1600059793&sct=7&seg=1&en=page_view&ep.origin=firebase

You can peek at my page if you want (see the URL in the link above?), but I'm working on this daily, and I can't promise it'll be in the same state tomorrow. I may just rip Analytics out of the project until we can get a better handle on this.

This seems like a mistake to me. I don't think the script should be injected twice. That's sloppy, and looks broken, which is not typical of Google. Worse, it's giving my page a bad performance rating--one that Google's own web crawler can certainly see, and may penalize me for.

@hsubox76
Copy link
Contributor

As mentioned in my previous comment, can you create a plain HTML page using gtag.js directly (https://developers.google.com/analytics/devguides/collection/gtagjs) and NOT importing or using Firebase, and let me know what problems you see in the Firebase page that you do not see in that page? We need to isolate which problems are unique to Firebase and not gtag.js.

You can use the same measurementId as your Firebase app.

@killianhuyghe
Copy link

I had this problem and managed to find a workaround for my use case.

My initial problem was the following: I am using Nextjs and was initialising both firebase app and firebase analytics in code that was running at build time (nodejs), which was working in my development setting (I got all the events in the DebugView) but was failing in the call to firebase.analytics() at build time when deploying my webapp. Note that I am not sure why it was working in my dev setting (running next dev), I was seeing the error in server logs (ReferenceError: navigator is not defined) but it would probably rerun the code on the client and work eventually.

My first idea was to move the analytics init to a part of the code that only runs in the client and not at build time (using the useEffect hook in React). This way I got rid of the build error but my events wouldn't be sent reliably anymore. I noticed the first couple events would sometime work and then nothing (maybe a race between the two inits of gtag, one not being configured properly?).

After playing around, I realised that if I keep the firebase.analytics() call at build time (right after the call to firebase.initializeApp(config)) but move it inside a try/catch block, I can build and I get the correct behaviour with all events being properly sent and visible in the DebugView.

Note that this fixed my issues with events not being sent (which was my only concern at this point) but that I still see the two gtag init messages in the console and keep seeing logs like Sending event "screen_view" to undefined (but these now don't impact the functionality of GA).

So my guess would be that there might be some kind of race condition in the initialisation of gtag that could prevent it from being configured correctly. Moving the analytics init to as early as possible fixed it for me and I had to protect it in a try statement, which probably make the script run far enough to configure gtag properly and then fails only at the end because it is running in nodeJS.

In my code I have a FirebaseProvider component that I include first in all my pages:

export default function FirebaseProvider({ children }) {
  if (!firebase.apps.length) {
    firebase.initializeApp(firebaseConfig);
    try {
      firebase.analytics();
    } catch (error) {}
  }

  ...
}

It's ugly but it works for me. Hope this helps.

@hsubox76
Copy link
Contributor

I believe the reason Firebase errors in your server-side build in Node is because Firebase Analytics is not supported in Node. I don't think this is related to this issue.

Let me try to sum up this issue so far:

  1. gtag is always included twice when using Firebase Analytics. This is expected. It is not a bug (in the sense of being unexpected at least) or a sign your build process is wrong or not working with Firebase. It is not ideal but not currently avoidable. See comment above: Analytics GTAG script installed twice #2628 (comment)

  2. In previous tests I didn't find any functionality issue that stemmed from gtag being included twice. There definitely could be an issue I didn't find, but in order to ensure the error is being caused by gtag being included twice and not by some other cause, we need to isolate the issue more narrowly than, seeing a functionality issue and also seeing gtag being included twice, because, as mentioned above, that happens in every case.

  3. Here's a general Firebase Analytics debugging guide to help determine if the problem is your environment: Analytics GTAG script installed twice #2628 (comment) It includes a website to test your environment on, as well as a minimal repo to test on your machine.

  4. To help isolate if the problem is definitely with Firebase (as opposed to gtag itself), please try to create a plain HTML page using gtag.js directly (https://developers.google.com/analytics/devguides/collection/gtagjs) and NOT importing or using Firebase, and let me know what problems you see in the Firebase page that you do not see in that page. You can use the same measurementId as your Firebase app. If you see the same problems in that page (not receiving events, console errors), it seems like the problem is with gtag.js itself. Also let me know, but in that case I can only report it.

@killianhuyghe
Copy link

Yes, it's not supported and most likely unrelated to the issue. The fact that we see gtag logs twice doesn't seem directly related to the issue either as I see this whether events are working or not.

My point is that the only difference between a working configuration and a failing one (events are not sent) on my project is the place/time where I initialise firebase analytics (potentially with regard to the place/time where I initialise the firebase app).

Sorry if that wasn't clear.

@henrik-d
Copy link

I have a similar issue. I have a minimalistic setup:

firebase.initializeApp(firebaseConfig);
firebase.analytics();

This causes gtag to be loaded twice:
gtag-analytics

I see that the first load is initiated by helpers.ts, where I find the following comment:

// We are not providing an analyticsId in the URL because it would trigger a `page_view`
// without fid. We will initialize ga-id using gtag (config) command together with fid.

Can you explain why this is necessary?

I tried loading gtag myself (with measurementId included) like that:

firebase.initializeApp(firebaseConfig);

const script = document.createElement('script');
script.src = `https://www.googletagmanager.com/gtag/js?l=dataLayer&id=${firebaseConfig.measurementId}`;
script.async = true;
document.head.appendChild(script);

firebase.analytics();

This way gtag is only loaded once. Is this a save approach, or can this lead to corrupted analytics data?

@hsubox76
Copy link
Contributor

The thing that Firebase analytics adds on top of using plain gtag is that it attaches a Firebase instance ID to every event sent to analytics, which allows the backend to associate that event with your Firebase project. If that ID is not sent, the event will be stored with Google Analytics (I believe) but will not be associated with your Firebase project and won't show up in your Firebase analytics dashboard.

If you include the measurementId in the gtag script download url as in your example above, it will immediately trigger a page_view event for that measurementID. This event data won't include your Firebase instance ID and won't be associated with your Firebase project.

By leaving the measurementID out of the script download url, we prevent the initial page_view event (with no Firebase ID) from being sent, and we immediately make a gtag config call to send the measurementId plus firebase ID as the first call, so that your page_view will be logged correctly to your Firebase project. Unfortunately gtag needs to make another download once it gets the measurementId for the first time, so that's why it loads again.

There doesn't seem to be a way to avoid it unless the gtag API changes to allow us to attach arbitrary config data to the initial script download, or some other option around this.

@henrik-d
Copy link

Thanks for the explanation. Unfortunately I can not reproduce this behaviour in my project. If I simply add the gtag script with measurementId, I do not see a page_view event in my network log (observing calls to https://www.google-analytics.com/g/collect). The page_view is not sent until I call firebase.analytics().

After initializing firebase analytics I see the following POST request:

POST https://www.google-analytics.com/g/collect?v=2&tid=[...]&gtm=2oe1k0&_p=2016064008&sr=1792x1120&ul=de&_fid=dNscZUShLO8o_OeXKY19wW&cid=1801948586.1608133652&_s=1&dl=http%3A%2F%2Flocalhost%2F&dr=&dt=React%20App&sid=1612022747&sct=50&seg=0&en=page_view&_ss=1&ep.origin=firebase

@hsubox76
Copy link
Contributor

Thanks very much for drawing my attention to measurementId in the script tag not causing a page_view event. I tested it and talked to the gtag team and confirmed this. I think we may have been operating on incorrect info originally.

I've just merged #4434 which I believe will stop gtag from being downloaded twice. I'm not sure if this will fix any errors people are experiencing or reporting in this thread, but perhaps it could. It should at least avoid adding unnecessary and confusing clutter while you are debugging those errors.

The fix probably won't go into this week's release (which is happening today) but should most likely make it into next Thursday's (Feb 18).

@rromanchuk
Copy link

i don't understand how this works with my own first party domain via GTM server side, can i pass a transport_url? https://analytics.my-custom-domain.com/gtag/js404s because thats not an expected query param

Can i just use my firebase web measurement id without firebase.analytics()? Right now i have firebase web data stream, and then i have a "web web" data stream because i dont trust the firebase web property since it's not managed by G4 (where is measurement protocol secrets api key?) so now i have two separate data streams to make sure i'm losing web to app conversions.

Aren't we really just talking about const installationId = await firebase.installations().getId(); which i'm assuming is the fb instance id required to send s2s g4 measurement protocol? I'm happy to hold off initialization on my own, and pass whatever it needs.

@rromanchuk
Copy link

Is there any way i can append or expose gtagCore("config" /* CONFIG */, dynamicConfig.measurementId, configProperties); Without hacking around with dom race conditions?

const configProperties = {
        // guard against developers accidentally setting properties with prefix `firebase_`
        [ORIGIN_KEY]: 'firebase',
        update: true
    };
async function initializeAnalytics(app, dynamicConfigPromisesList, measurementIdToAppId, installations, gtagCore, dataLayerName) {
    const dynamicConfigPromise = fetchDynamicConfigWithRetry(app);
    // Once fetched, map measurementIds to appId, for ease of lookup in wrapped gtag function.
    dynamicConfigPromise
        .then(config => {
        measurementIdToAppId[config.measurementId] = config.appId;
        if (app.options.measurementId &&
            config.measurementId !== app.options.measurementId) {
            logger.warn(`The measurement ID in the local Firebase config (${app.options.measurementId})` +
                ` does not match the measurement ID fetched from the server (${config.measurementId}).` +
                ` To ensure analytics events are always sent to the correct Analytics property,` +
                ` update the` +
                ` measurement ID field in the local config or remove it from the local config.`);
        }
    })
        .catch(e => logger.error(e));
    // Add to list to track state of all dynamic config promises.
    dynamicConfigPromisesList.push(dynamicConfigPromise);
    const fidPromise = validateIndexedDB().then(envIsValid => {
        if (envIsValid) {
            return installations.getId();
        }
        else {
            return undefined;
        }
    });
    const [dynamicConfig, fid] = await Promise.all([
        dynamicConfigPromise,
        fidPromise
    ]);
    // Detect if user has already put the gtag <script> tag on this page.
    if (!findGtagScriptOnPage()) {
        insertScriptTag(dataLayerName, dynamicConfig.measurementId);
    }
    // This command initializes gtag.js and only needs to be called once for the entire web app,
    // but since it is idempotent, we can call it multiple times.
    // We keep it together with other initialization logic for better code structure.
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    gtagCore('js', new Date());
    const configProperties = {
        // guard against developers accidentally setting properties with prefix `firebase_`
        [ORIGIN_KEY]: 'firebase',
        update: true
    };
    if (fid != null) {
        configProperties[GA_FID_KEY] = fid;
    }
    // It should be the first config command called on this GA-ID
    // Initialize this GA-ID and set FID on it using the gtag config API.
    // Note: This will trigger a page_view event unless 'send_page_view' is set to false in
    // `configProperties`.
    gtagCore("config" /* CONFIG */, dynamicConfig.measurementId, configProperties);
    return dynamicConfig.measurementId;
}

@hsubox76
Copy link
Contributor

There is a PR in progress to add this functionality (provide custom config params at initialization) in the new modular Firebase API (which is currently in Beta): #4575

You can use gtag without Firebase Analytics using your Firebase project's measurement ID. The drawback is you'll lose out on some functionality that lets you use things like Analytics audiences with other Firebase products like Remote Config.

@FBosler
Copy link

FBosler commented Jun 15, 2021

@johndunderhill

@hsubox76, thank you for your detailed reply. To be clear, my concern is the bahavior of Firebase Analytics when initialized. @tzar, this has nothing to do with frameworks. I can re-create the problem with a plain HTML page. I have not found anything that will stop this.

After importing Firebase from the CDN (version 7.19.1), and initializing Firebase, I initialize Analytics. At that point, the Google library injects two scripts into the head of my page, one of which appears to be superflous. In addition, it makes two POST calls, one of which never resolves, leading the browser and performance monitoring tools like Lighthouse to think the page is still loading.

Here are the two scripts injected into the head:

<script type="text/javascript" async="" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX&amp;l=dataLayer&amp;cx=c"></script>
<script src="https://www.googletagmanager.com/gtag/js?l=dataLayer" async=""></script>

This call stays pending forever:

https://www.google-analytics.com/g/collect?v=2&tid=G-XXXXXXXXXX&gtm=2oe920&_p=1584065445&sr=1920x1080&ul=en-us&_fid=cGypl-WCHc8S3LcQnzTKci&cid=1765209537.1599819643&_s=2&dl=https%3A%2F%2Fcostvine.app%2F&dr=&dt=Costvine%20%E2%80%A2%20Budgets%20made%20easy&sid=1600059793&sct=7&seg=0&en=user_engagement&_et=2966&ep.origin=firebase

This call returns quickly with a 204:

https://www.google-analytics.com/g/collect?v=2&tid=G-XXXXXXXXXX&gtm=2oe920&_p=1208409921&sr=1920x1080&ul=en-us&_fid=cGypl-WCHc8S3LcQnzTKci&cid=1765209537.1599819643&_s=1&dl=https%3A%2F%2Fcostvine.app%2F&dr=&dt=Costvine%20%E2%80%A2%20Budgets%20made%20easy&sid=1600059793&sct=7&seg=1&en=page_view&ep.origin=firebase

You can peek at my page if you want (see the URL in the link above?), but I'm working on this daily, and I can't promise it'll be in the same state tomorrow. I may just rip Analytics out of the project until we can get a better handle on this.

This seems like a mistake to me. I don't think the script should be injected twice. That's sloppy, and looks broken, which is not typical of Google. Worse, it's giving my page a bad performance rating--one that Google's own web crawler can certainly see, and may penalize me for.

Heya, have you ever figured out, why some requests are stuck in pending I am seeing the same behaviour. Cheer

@rromanchuk
Copy link

I'm using S2S via G4 Measurement protocol for mobile events. Is it theoretically possible to use https://firebase.google.com/docs/reference/js/installations.md#getid getId() to send REST events to the web G4/fb data stream, with this method as well (with the installation id returned from the js 9 client)?

Or, if i'm using a GTM web container<=>server with G4 tags can i push the firebase installation id to the dataLayer to be passed to g4 collection to be consumed as if it were coming from the the firebase analytics client? Basically if you look at the architecture here https://developers.google.com/analytics/devguides/collection/protocol/ga4

In this context, web client is referring to a non-firebase concept of client id, a gtag concept. Couldn't i retrieve the firebase installation from the web the same way as native client, and consolidate both data streams in an S2S only approach?

I'd be happy to manage web installation id myself. My objective is just being able to send events to a firebase web property/data stream without the use of cross domain browser javascript/sdk

@hsubox76
Copy link
Contributor

I'm going to close this issue, as the initial issue of gtag being installed twice has been addressed. I realize the last 2 comments are old and had not been addressed so sorry about that, but if anyone is having separate issues or questions with Analytics not related to gtag being installed twice, feel free to make a new issue.

@firebase firebase locked and limited conversation to collaborators Jul 17, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests