If you want to configure GA4 with code instead of using Tag Manager, here's how I did it. The example uses Remix, but it should be very similar with any React framework.
I've spent quite a few hours configuring GA4 with React, plus a cookie banner and consent mode. I hope it'll be faster next time thanks to these notes. I'll separate the consent mode stuff into another post to keep things simple.
Disclaimer: there might still be issues here, I'll update the post as I find them. But the tag is being recognized and there are events firing.
The gtag code
After creating your Google Analytics account, property and stream, at some point you'll see this code:
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-C33KHSZBKZ"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-.........');
</script>
It has two parts:
- Loading the gtag.js script: the async one
- Configuring the datalayer array and gtag function
They are NOT the same thing! If you don't need to add a cookie banner to get the user consent, you don't need to worry about it. But if you need to comply with the GDPR or with Brazil's LGPD, you will need to understand a bit more. That will be the theme of a future post.
GA hook
Let's go with the simple implementation, a custom hook.
This hook can be called multiple times if needed, and it will ensure the initialization runs only once. It has a convenient return to let you know if it is ok to use GA.
const useGoogleAnalytics = (
gaMeasurementId: string | undefined,
) => {
const [isInitialized, setIsInitialized] = useState(false)
useEffect(() => {
const loadScript = () => {
// gaMeasurementId is optional so not all devs need it to run the app
if (!window.gtag && gaMeasurementId) {
// Create the script element
const script = document.createElement('script')
script.src = `https://www.googletagmanager.com/gtag/js?id=${gaMeasurementId}`
script.async = true
// Append the script to the document
document.head.appendChild(script)
// Initialize gtag when the script is loaded - this could be done before
script.onload = () => {
window.dataLayer = window.dataLayer || []
function gtag() {
window.dataLayer.push(arguments)
}
window.gtag = gtag
window.gtag('js', new Date())
window.gtag('config', gaMeasurementId, {
debug_mode: false, // I keep this here to remember where to toggle debug
})
// Mark as initialized
setIsInitialized(true)
}
} else {
// gtag is already available, mark as initialized
setIsInitialized(true)
}
}
loadScript()
}, [gaMeasurementId, analyticsAnonymousId])
return isInitialized
}
Caveat
This code creates a single script that loads GA immediately; if you need to comply with some privacy laws, you can't do it this way! The external script can only be loaded after getting the user's consent. I'll show how I did it in a future post.
Usage
Here's an example of how to track page views:
// root.tsx
export async function loader({ request, params }: LoaderFunctionArgs) {
return json(
{
gaMeasurementId: process.env.GA_MEASUREMENT_ID, // or however you manage and protect your env vars. Not strictly necessary, but I prefer to manage all of them from the server
},
)
}
function GoogleAnalytics() {
const location = useLocation()
const { gaMeasurementId } =
useLoaderData<typeof loader>()
const isGaInitialized = useGoogleAnalytics(
gaMeasurementId,
analyticsAnonymousId
)
useEffect(() => {
if (isGaInitialized && gaMeasurementId) {
window.gtag('config', gaMeasurementId, {
page_path: location.pathname,
})
}
}, [isGaInitialized, location, gaMeasurementId])
return null
}
export default function Component() {
return (
<>
<GoogleAnalytics />
<Outlet />
</>
Function to track events
You can use similar code to register any client-side events or user properties you might need. just call window.gtag('event', ...)
following Google's API.
Here's a reusable example:
// analytics.client.ts
import type { SnakeCase } from 'string-ts'
function trackClientAnalyticsEvent<T extends string>(
eventName: T & SnakeCase<T>, // GA only supports snake_case event names. Let's enforce it at type-level to make life easier
properties?: Record<string, unknown>
) {
return window.gtag && window.gtag('event', eventName, properties)
}
export { trackClientAnalyticsEvent }
In my case, I'm only using that function to register a few clicks, since most events will be registered server-side.
...
<Link
to={`...`}
onClick={() =>
trackClientAnalyticsEvent('user_clicked_this_link')
}
>
Call To Action!
</Link>
...
This should be enough to get you started. Now you can go on to figure out consent mode and server-side events with the measurement api. Good luck!
Top comments (0)