DEV Community

Cover image for Google Analytics (GA4) implementation with React - Remix example
Felipe Freitag Vargas for Seasoned

Posted on

Google Analytics (GA4) implementation with React - Remix example

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>
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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 />
    </>

Enter fullscreen mode Exit fullscreen mode

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 }
Enter fullscreen mode Exit fullscreen mode

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>
...
Enter fullscreen mode Exit fullscreen mode

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)