Skip to content
This repository has been archived by the owner on Jan 8, 2025. It is now read-only.

Add Google Ads Conversion tracking #458

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
71 changes: 65 additions & 6 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import Script from "next/script";
import type { AppProps } from "next/app";
import { DocsContextProvider } from "layouts/DocsPage/context";
import { posthog, sendPageview } from "utils/posthog";
import { posthog, sendEngagedView, sendPageview } from "utils/posthog";
import { TabContextProvider } from "components/Tabs";

// https://larsmagnus.co/blog/how-to-optimize-custom-fonts-with-next-font
Expand Down Expand Up @@ -54,10 +54,12 @@ export const lato = localLato({

import "styles/varaibles.css";
import "styles/global.css";
import { GoogleAdsEvent } from "utils/tracking";

const NEXT_PUBLIC_REDDIT_ID = process.env.NEXT_PUBLIC_REDDIT_ID;
const NEXT_PUBLIC_GTM_ID = process.env.NEXT_PUBLIC_GTM_ID;
const NEXT_PUBLIC_GTAG_ID = process.env.NEXT_PUBLIC_GTAG_ID;
const NEXT_PUBLIC_GOOGLE_ADS_ID = process.env.NEXT_PUBLIC_GOOGLE_ADS_ID;
const MUNCHKIN_ID = process.env.MUNCHKIN_ID;

interface dataLayerItem {
Expand All @@ -68,7 +70,32 @@ interface dataLayerItem {
declare global {
var dataLayer: dataLayerItem[]; // eslint-disable-line no-var
}
const useIsEngaged = () => {
const router = useRouter();
const [timerReached, setTimerReached] = useState(false);
const [secondPageReached, setSecondPageReached] = useState(false);
const [isEngaged, setIsEngaged] = useState(false);

useEffect(() => {
setTimeout(() => {
setTimerReached(true);
}, 30000);
const routeChanged = () => {
setSecondPageReached(true);
};

router.events.on("routeChangeComplete", routeChanged);
return () => {
router.events.off("routeChangeComplete", routeChanged);
};
}, [router.events]);

useEffect(() => {
setIsEngaged(secondPageReached && timerReached);
}, [secondPageReached, timerReached]);

return isEngaged;
};
const Analytics = () => {
return (
<>
Expand Down Expand Up @@ -152,6 +179,9 @@ const Analytics = () => {
gtag('js', new Date());
gtag('config', "${NEXT_PUBLIC_GTAG_ID}", {
send_page_view: false
});
gtag('config', "${NEXT_PUBLIC_GOOGLE_ADS_ID}", {
send_page_view: false
});`}
</Script>
{/* End GTag */}
Expand All @@ -163,6 +193,18 @@ const Analytics = () => {
{/* End Google Tag Manager (noscript) */}
</>
)}
{/* Quailified Script */}
<Script id="script_qualified">
{`(function (w, q) {
w["QualifiedObject"] = q;
w[q] =
w[q] ||
function () {
(w[q].q = w[q].q || []).push(arguments);
};
})(window, "qualified")`}
</Script>
<Script src="https://js.qualified.com/qualified.js?token=GWPbwWJLtjykim4W" />

{NEXT_PUBLIC_REDDIT_ID && (
<>
Expand All @@ -180,14 +222,31 @@ const Analytics = () => {

const MyApp = ({ Component, pageProps }: AppProps) => {
const router = useRouter();
const isEngaged = useIsEngaged();

useEffect(() => {
posthog(); // init posthog

router.events.on("routeChangeComplete", sendPageview);
if (!isEngaged) return;
// Trigger engagement view events here
GoogleAdsEvent("30sView");
sendEngagedView();
}, [isEngaged]);

const Pageviews = () => {
// Trigger page views here
// Google Ads Docs Page Conversion event
GoogleAdsEvent("DocsPageView");
// Qualified page view
if (!!window["qualified"]) window["qualified"]("page");
// Posthog page view
sendPageview();
};
useEffect(() => {
posthog(); // init posthog
// Trigger initial load page views
Pageviews();
router.events.on("routeChangeComplete", Pageviews);
return () => {
router.events.off("routeChangeComplete", sendPageview);
router.events.off("routeChangeComplete", Pageviews);
};
}, [router.events]);

Expand Down
8 changes: 8 additions & 0 deletions utils/posthog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,11 @@ export const sendDocsFeedback = async (rating: string, comment: string) => {
"web.docs.comment": comment,
});
};

export const sendEngagedView = async () => {
const ph = await posthog();
ph?.capture("web.engagement.timer", {
"web.engagement.sessionTimerThreshold": 30,
"web.engagement.sessionPageThreshold": 2,
});
};
47 changes: 47 additions & 0 deletions utils/tracking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export const isGtagEnabled = () => {
return typeof window !== "undefined" && !!window.dataLayer;
};
type ConversionLabel = "DocsPageView" | "30sView";
export const GoogleAdsEvent = (
conversion_label: ConversionLabel,
payload: Record<string, unknown> = {}
): Promise<void> => {
return new Promise<void>((resolve) => {
if (isGtagEnabled()) {
const getLabel = (label: ConversionLabel) => {
switch (label) {
case "DocsPageView":
return "bhkmCKaO5_AYEN2k6cID";
case "30sView":
return "Dt85CKqO7vAYEN2k6cID";
}
};
// Define that we're sending a Google Ads conversion event
// https://developers.google.com/tag-platform/devguides/events#google_ads_conversions
// the gtag() function in the docs effectively just calls window.dataLayer.push()
window.gtag("event", "conversion", {
// send_to: Defines the type of conversion we're sending
send_to: `${process.env["NEXT_PUBLIC_GOOGLE_ADS_ID"]}/${getLabel(
conversion_label
)}`,
// Payload: rest of the values for the conversion
...payload,
/*eventCallback and eventTimeout are not found in GTM official docs!
eventCallback is a function which will execute when all tags which fire on
the event have executed; it is scoped to this promise. Always add eventTimeout
when you use eventCallback.
*/
eventCallback: () => resolve(),

/*eventTimeout takes a number in milliseconds as a value after which it calls eventCallback, so
even if the tags don't fire or signal completion, eventCallback will be invoked (and
this promise resolved)
*/
eventTimeout: 1000,
});
} else {
console.log("Google Ads Tracking Event", payload);
resolve();
}
});
};
Loading