loading...

Server Side React + Google Analytics Event Tagging

saragibby profile image Sara Gibbons ・5 min read

Often times whether you are working on client or server side rendered React it all feels the same. All the same familiar pieces and how you develop. It isn't until you hit a dark corner on a server side rendered (SSR) React app where you get a window undefined message that you begin to question your life choices. Google Analytics event tagging is one of those dark corners.

Getting Google Analytics Wired In

Now, tracking page views to Google Analytics, pretty straight forward for an SSR React app. For those of you who haven't dug into that, here is the pieces that make that happen:

1. Just like the docs say, "Add gtag.js to your site"

This translates to somewhere incorporated into your app you will have something along the lines of:

import React from 'react';
import Head from 'next/head';

import { GA_TRACKING_ID } from '../lib/gtag';

const Meta = () => (
  <Head profile="http://www.w3.org/2005/10/profile">
    <title key="title">My Awesome Website Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta charSet="utf-8" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <meta
      name="description"
      content="The super neat description of my site"
    />
    <meta
      name="keywords"
      content="saragibby, sara, gibby, who runs the world"
    />

    <script
      async
      src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
    />

    <script
      dangerouslySetInnerHTML={{
        __html: `
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', '${GA_TRACKING_ID}');
          `,
      }}
    />
  </Head>
);

export default Meta;

Where the value of GA_TRACKING_ID is the tracking identifier assigned to your Analytics site.

Once you have this baked in you are up and running. Each time this snippet renders it will send a pageview hit to the connected Google Analytics account.

In the example above, I have this rendering as part of the layout of a page. Which means each time the layout is rendered a ding to the pageview hits. Which gives us our log of each pageview for the site.

I haven't yet found many situations where this doesn't track a pageview as expected. I've read a couple of people recommend adding a trigger to Analytics when the route changes, something like:

Router.events.on('routeChangeComplete', url => gtag.pageview(url));

Test your site, see if you need it. Some people reference shallow rendered components, but I just care about the page anyway, the other intel I want is in the user behavior... the event tagging.

Let's get those events tagging in Analytics

You got Google Analytics tracking your page views, but now what you really want to know is things like "Which of the buttons gets the most clicks?"... "How many people click to 'show more'?" I know! I love all those juicy deets too!

But Analytics isn't going to make this an easy journey for server-side rendered app. Now, you can pretty easily follow the docs and get a client-side rendered up hooked up for event tagging. But, server-side, if you have tried before to log events you've most likely hit the "window undefined" error. Cause, well it is. You are on the server, there is no window at the time of render.

If you have hit this in other areas of your application you most likely found you can get around this by using componentDidMount to access the window. But add a componentDidMount to each component you want to track events on so that you can et around the window undefined error for Analytics, less than ideal.

What you can do is add a a couple of functions that will trigger each other on a client side event.

First piece, create a shared lib or util file for your Google Analytics functions (now this assumes you have Analytics wired into your app like above). You will most likely add to this file as your app grows, but to start it may just have this function:

// relative path to file: lib/gtag.js

export const event = ({ clientWindow, action, category, label, value }) => {
  clientWindow.gtag('event', action, {
    event_category: category,
    event_label: label,
    value,
  });
};

This gives you an event function you can call, passing in the values you want to track for the given event. You can then import this function to the component that has the onClick (or whatever other) event you want to add a tag in Analytics for.

You'll see we pass in clientWindow here as opposed to just getting window within the function. We do this cause when the import happens during the server-side render, when window will still be undefined. If we trigger this function on the client-side event the window will exist and we can pass it in as an argument.

Here is how that will look in your component:

import * as gtag from '../../lib/gtag';

const CoolLink = ({ className, linkText }) => {
  const clickTracking = e => {
    gtag.event({
      clientWindow: window,
      action: 'click',
      category: 'link button',
      label: props.label,
    });
  };

  return (
    <a
      href={props.href}
      onClick={clickTracking}
      className={className}
    >
      {linkText}
    </a>
  );
};

In this example, the clickTracking function for CoolLink is only fired for the client-side click event on the anchor tag. It sends the event tag to Analytics and then completes the default/expected behavior of the link. The best part window is defined here!

Now, one change can spoil the fun. Let's look close at this line onClick={clickTracking}. Here the clickTracking function is being passed in but not evaluated. So when in renders on the server it is not evaluating the part of that function referencing window.

If you change this line to onClick={clickTracking()}, with the added parens on the function, the function will evaluate on render of the component. In this case, that means it will render on the server, hit the call to window and spit out the window is undefined error.

Sweet, but I need to pass an argument to my tracking function, halp!

I got you. Here is what you need to do... change your clickTracking function to take the arguments you need:

  const clickTracking = (e, label) => {
    gtag.event({
      clientWindow: window,
      action: 'click',
      category: 'link button',
      label: label,
    });
  };

You will still trigger this function onClick but remember we don't want it to evaluate so we can't go with our instincts here to add on onClick={clickTracking('newsletter')}. We still want to pass in a function that will evaluate onClick, so that is exactly what we are going to do:

onClick={() => clickTracking('newsletter')}

There you go, track your events, report on all the things and enjoy the server-side rendered goodness!

Discussion

pic
Editor guide