July 26, 2022 Author • Paul Scanlon

How to use Gatsby's Script API with Google Analytics

  • Gatsby
  • React
  • JavaScript
  • Partytown
  • Google Analytics

In this post I'll be explaining how to add Google's "new" Google Analytics Property (GA4) to your Gatsby Site using the new Gatsby Script API.

I'll be demonstrating how to use the off-main-thread strategy which is powered by Builder'io's Partytown. I wrote about this once before on the Gatsby Blog, but that was before the release of Gatsby 4.15.0. From 4.15.0 onwards Gatsby takes care of all of the nitty-gritty for you so there's considerably less config required.

This post focuses on the Google Analytics 4 Property rather than the more recognizable Universal Analytics (UA) property. Here's a little note from Google about why you should use GA4 rather than UA.

Universal Analytics will no longer process new data in standard properties beginning July 1, 2023. Prepare now by setting up and switching over to a Google Analytics 4 property

Below are links to a minimal example repository on GitHub, and a Gatsby Cloud preview URL.

I won't go through the steps to create a GA4 Property in this post, but here's a link to Google's docs that should help you on your way.

Getting Started

Upgrade

The Script API was released as part of Gatsby 4.15.0, make sure you're on at least this version or upgrade to the latest version of Gatsby. Here's a link to the Gatsby Changelog Prototype where you can see all the released versions and more details about each release.

npm install gatsby@latest

Remove Plugin

It's likely you'll have been using gatsby-plugin-google-analytics, but after upgrading Gatsby you can uninstall it, and remove it from the plugins array in gatsby-config.

// gatsby-config.js
module.exports = {
plugins: [
...
- {
- resolve: 'gatsby-plugin-google-analytics',
- options: {
- trackingId: 'UA-12345678-9'
- }
- }
],
};

Partytown Proxy

Since you're in gatsby-config.js you'll need to add the following.

I've explained in more detail how Partytown proxies requests from Web Workers and how this usually ends up with unfathomable CORS errors in this post: How to Add Google Analytics gtag to Gatsby Using Partytown 🎉

// gatsby-config.js
module.exports = {
plugins: [
...
],
+ partytownProxiedURLs: [`https://www.googletagmanager.com/gtag/js?id=${process.env.GATSBY_GA_MEASUREMENT_ID}`]
};

Adding Scripts

This next bit entirely depends on how you've setup your Gatsby Site.

Shared Component

It's quite common however to have a "shared" React component that is returned by wrapRootElement in both gatsby-browser.js and gatsby-ssr.js. There's a little more in the Gatsby Docs here: Usage in Gatsby SSR and Browser APIs

In the example repo I've called this Component RootElement, and it looks a bit like this.

// src/components/root-element.js
import React, { Fragment } from 'react';
import { Script } from 'gatsby';
const RootElement = ({ children }) => {
return (
<Fragment>
<Script
src={`https://www.googletagmanager.com/gtag/js?id=${process.env.GATSBY_GA_MEASUREMENT_ID}`}
strategy="off-main-thread"
forward={[`gtag`]}
/>
<Script
id="gtag-config"
strategy="off-main-thread"
dangerouslySetInnerHTML={{
__html: `window.dataLayer = window.dataLayer || [];
window.gtag = function gtag(){ window.dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${process.env.GATSBY_GA_MEASUREMENT_ID}', { send_page_view: false })`
}}
/>
<div>{children}</div>
</Fragment>
);
};
export default RootElement;

The RootElement Component is returned by wrapRootElement in both gatsby-browser.js and gatsby-ssr.js which looks a bit like this.

// gatsby-browser.js
import React from 'react';
import RootElement from './src/components/root-element';
export const wrapRootElement = ({ element }) => {
return <RootElement>{element}</RootElement>;
};
// gatsby-ssr.js
import React from 'react';
import RootElement from './src/components/root-element';
export const wrapRootElement = ({ element }) => {
return <RootElement>{element}</RootElement>;
};

If you don't ensure that gatsby-browser.js and gatsby-ssr return the same DOM elements you'll likely see a React error like this.

Hydration failed because the initial UI does not match what was rendered on the server

You can read more about what that error means in the React Docs: Error Decoder

Page Views

You'll notice from the above that in the gtag config send_page_view is set to false. This is just for the initial setup but naturally you'll want to fire off page_view events... because that's what Google Analytics is all about amirite?

Send Page Views

onRouteUpdate is one of Gatsby Browser's API's and fires whenever a route change is detected. This API has a destructured parameter for the current location. This is perfect for sending to Google's page_view and will show up in your Analytics Dashboard.

Here's how I've implemented it in gatsby-browser.js. You can see the complete src from the example repo here: gatsby-browser.js

// gatsby-browser.js
import React from 'react';
import RootElement from './src/components/root-element';
+ export const onRouteUpdate = ({ location }) => {
+ if (process.env.NODE_ENV !== 'production') {
+ return null;
+ }
+ const pagePath = location ? location.pathname + location.search + location.hash : undefined;
+ setTimeout(() => {
+ if (typeof window.gtag === 'function') {
+ window.gtag('event', 'page_view', { page_path: pagePath });
+ }
+ }, 100);
+ return true;
+ };
export const wrapRootElement = ({ element }) => {
return <RootElement>{element}</RootElement>;
};

Google Admin Dashboard

This tripped me up but, make sure you've added your site URL to the Data Streams section of your Google Analytics Dashboard otherwise Google won't be "listening" out of page_view events.

Screenshot of Google Analytics Data Streams

And Finally

Here's a little screenshot of my Google Analytics Realtime Overview, et voila, there I am on the map. It works, lovely stuff.

Screenshot of Google Analytics Realtime Overview

Further Reading

Here's a couple of helpful links that should give you all the information you need to get going with the Script API.

How am I doing?

Hey! Lemme know if you found this helpful by leaving a reaction.