skies.dev

Google Analytics Events with Gatsby and TypeScript

3 min read

Context

If you track user behavior in Gatsby, TypeScript can do more than catch obvious mistakes. It can also make your analytics schema explicit so event names stay consistent across the app.

The pattern in this post is simple:

  • Define the finite set of categories, actions, and labels in one file.
  • Wrap trackCustomEvent so the rest of the app uses your typed helper.
  • Allow a small escape hatch for labels that must be generated at runtime.
npm install gatsby-plugin-google-analytics

This post focuses on the event schema and tracking code. It assumes you already have Google Analytics wired up in Gatsby.

Model Analytics Events with Types

A typical analytics event has three parts:

  • Category: what part of the product was involved
  • Action: what the user did
  • Label: which specific item or context was involved

Instead of scattering string literals throughout the codebase, I keep those values in a single analytics.ts module.

analytics.ts
export enum AnalyticsCategory {
  Themes = 'Themes',
  Newsletter = 'Newsletter',
  Blog = 'Blog',
  Feedback = 'Feedback',
  Contact = 'Contact',
}

export enum AnalyticsAction {
  Click = 'Click',
  Subscribe = 'Subscribe',
  Toggle = 'Toggle',
  SendEmail = 'Send Email',
  Like = 'Like',
  Dislike = 'Dislike',
  Dismiss = 'Dismiss',
}

export enum AnalyticsLabel {
  Light = 'Light',
  Dark = 'Dark',
  Green = 'Green',
  Blue = 'Blue',
  Red = 'Red',
}

export interface AnalyticsEvent {
  category: AnalyticsCategory;
  action: AnalyticsAction;
  label?: AnalyticsLabel | string;
}

That interface gives me a single shape for every event. Most labels come from the enum, but some need runtime data such as a pathname or page title.

Use the Typed Schema at the Call Site

Here is a simple example of tracking a click on a button. The event is still flexible, but the category and action now come from a fixed set of values.

Button.tsx
import {useLocation} from '@reach/router';
import {trackCustomEvent} from 'gatsby-plugin-google-analytics';
import {
  AnalyticsAction,
  AnalyticsCategory,
  AnalyticsEvent,
} from '@utils/analytics';

function Button() {
  const {pathname} = useLocation();

  return (
    <button
      type="button"
      onClick={() => {
        trackCustomEvent({
          category: AnalyticsCategory.Feedback,
          action: AnalyticsAction.SendEmail,
          label: pathname,
        });
      }}
    >
      Click
    </button>
  );
}

The main benefit here is consistency. If a new button is added later, it is much harder for someone to invent a new string like Send mail, send-email, or email sent and silently split the analytics data.

Add a Thin Wrapper

gatsby-plugin-google-analytics already exposes trackCustomEvent, but the plugin type definitions do not know about my own AnalyticsEvent interface. A tiny wrapper keeps the rest of the app on the typed API I control.

analytics.ts
import {trackCustomEvent} from 'gatsby-plugin-google-analytics';

export function track(event: AnalyticsEvent) {
  trackCustomEvent(event);
}

Once that wrapper exists, components can depend on track() instead of importing the plugin directly.

Button.tsx
import {useLocation} from '@reach/router';
import {
  AnalyticsAction,
  AnalyticsCategory,
  AnalyticsEvent,
  track,
} from '@utils/analytics';

function Button() {
  const {pathname} = useLocation();

  return (
    <button
      type="button"
      onClick={() => {
        const event: AnalyticsEvent = {
          category: AnalyticsCategory.Feedback,
          action: AnalyticsAction.SendEmail,
          label: pathname,
        };

        track(event);
      }}
    >
      Click
    </button>
  );
}

Why This Helps

This setup keeps analytics readable and maintainable:

  • Event values are centralized.
  • The compiler catches typos in category and action names.
  • Runtime labels are still possible when static values are not enough.
  • The rest of the app stays unaware of the plugin’s internal types.

That is the main advantage of using TypeScript for analytics: the schema becomes part of the code, not just an informal naming convention.

Hey, you! 🫵

Did you know I created a YouTube channel? I'll be putting out a lot of new content on web development and software engineering so make sure to subscribe.

(clap if you liked the article)

You might also like