120

I'm trying set up Google Analytics on my react site, and have come across a few packages, but none of which has the kind of set up that I have in terms of examples. Was hoping someone could shed some light on this.

The package I'm looking at is, react-ga.

My render method on my index.js looks like this.

React.render((
<Router history={createBrowserHistory()}>
    <Route path="/" component={App}>
        <IndexRoute component={Home} onLeave={closeHeader}/>
        <Route path="/about" component={About} onLeave={closeHeader}/>
        <Route path="/gallery" component={Gallery} onLeave={closeHeader}/>
        <Route path="/contact-us" component={Contact} onLeave={closeHeader}>
            <Route path="/contact-us/:service" component={Contact} onLeave={closeHeader}/>
        </Route>
        <Route path="/privacy-policy" component={PrivacyPolicy} onLeave={closeHeader} />
        <Route path="/feedback" component={Feedback} onLeave={closeHeader} />
    </Route>
    <Route path="*" component={NoMatch} onLeave={closeHeader}/>
</Router>), document.getElementById('root'));
the
  • 21,007
  • 11
  • 68
  • 101
John Fu
  • 1,812
  • 2
  • 15
  • 20

15 Answers15

102

Keep a reference to your history object. i.e.

import { createBrowserHistory } from 'history';

var history = createBrowserHistory();

ReactDOM.render((
    <Router history={history}>
        [...]

Then add a listener to record each pageview. (This assumes you've already set up the window.ga object in the usual manner.)

history.listen((location) => {
    window.ga('set', 'page', location.pathname + location.search);
    window.ga('send', 'pageview');
});
David L. Walsh
  • 24,097
  • 10
  • 61
  • 46
  • 16
    This won't account for events or other hit types that are sent. They'll still be referencing the URL at page load time. Instead you'll need to set the new value on the tracker before sending the pageview, e.g. `ga('set', 'page', location.pathname + location.search); ga('send', 'pageview');`. – Philip Walton Jan 17 '16 at 22:00
  • 2
    Hi David, is your example using regular ga code from the ga site or is it using the react-ga package? Thanks. – John Fu Feb 02 '16 at 00:09
  • Haven't decided how to fix it, yet, but this piece of info might also be useful: http://stackoverflow.com/questions/30052693/google-analytics-via-tag-manager-loses-track-of-adwords-visitors-after-first-p (it explains why attribution might not be working correctly in some cases and also introduce high bounce-rates). – DeTeam Oct 25 '16 at 09:02
  • You don't want third parameter on the send command. "While technically the send command for pageview hits accepts an optional page field as the third parameter, passing the page field that way is not recommended when tracking single page applications. This is because fields passed via the send command are not set on the tracker—they apply to the current hit only. Not updating the tracker will cause problems if your application sends any non-pageview hits (e.g. events or social interactions), as those hits will be associated with whatever page value the tracker had when it was created." – Joshua Robinson Sep 12 '18 at 17:36
  • https://developers.google.com/analytics/devguides/collection/analyticsjs/single-page-applications – Joshua Robinson Sep 12 '18 at 17:37
  • always go with the library's recommended way: https://stackoverflow.com/a/52464888/7377984 – Paras Sep 23 '18 at 09:52
  • This pattern worked for me eventually, but I was using google tag manger, and I had to make the following tweaks to get it to work with google tag manager. `history.listen(location => { const tracker = window.ga.getAll()[0]; if (tracker && tracker.set && tracker.send) { tracker.set("page", location.pathname); tracker.send("pageview"); } });` – Robotronic Dec 23 '19 at 03:38
65

The question is about react-ga but this package will soon be obsolete because it doesn't support Google Analytics 4. Below is a generic solution that works with any library or native gtag. For adding GA4 to React check out this answer: https://stackoverflow.com/a/73354959/2771889.

Since react-router v5.1.0 this can be solved a lot easier with useLocation.

usePageTracking.js

import { useEffect } from "react";
import { useLocation } from "react-router-dom";

const usePageTracking = () => {
  const location = useLocation();

  useEffect(() => {
    // track pageview with gtag / react-ga / react-ga4, for example:
    window.gtag("event", "page_view", {
      page_path: location.pathname + location.search,
    });
  }, [location]);
};

export default usePageTracking;

App.js

const App = () => {
  usePageTracking();

  return (...);
};

Keep in mind that Google Analytics does automatic page tracking, but this will not work for every use case. For example, hash and search parameter changes are not tracked. This can lead to a lot of confusion. For example, when using HashRouter or anchor links the navigation will not be tracked. To have full control over page view tracking you can disable automatic tracking. See for a detailed explanation: The Ultimate Guide to Google Analytics (UA & GA4) on React (Or Anything Else

You can see this working in cra-typescript-starter where I'm using it with GA4.

Dimitri Kopriwa
  • 13,139
  • 27
  • 98
  • 204
thisismydesign
  • 21,553
  • 9
  • 123
  • 126
  • 3
    I'm not sure it's necessary with the latest 'gtag'. When I navigate, ga debugger seems to correctly record the push event: `Processing data layer push: {event: "gtm.historyChange-v2", gtm.historyChangeSource: "pushState", gtm.oldUrlFragment: "", gtm.newUrlFragment: "", gtm.oldHistoryState: null, gtm.newHistoryState: {key: "j5xoc4", state: undefined}, gtm.oldUrl: "https://site/", gtm.newUrl: "https://site/new-url?search-params", gtm.triggers: "1_36"}` and a new page view is shown in the ga dashboard – Dattaya Nov 04 '20 at 16:07
  • 4
    the best and elegant solution I found, thank you. Just a consideration, to call useLocation in App.js you must to add in index.js like this: importing Router on the top with import { BrowserRouter as Router } from 'react-router-dom'; – Francesco Orsi Aug 11 '21 at 21:37
  • @FrancescoOrsi that's definitely right and with TypeScript it will warn you about the missing Route component – Marcello DeSales May 06 '22 at 09:58
  • @thisismydesign Any idea why page_view is tracked twice? My useEffect fires only once per route change as expected, but page_view is tracked twice. – fabpico Aug 17 '22 at 14:44
  • @fabpico disable automatic page tracking if you manually track page views: https://developers.google.com/analytics/devguides/collection/gtagjs/pages#disable_pageview_measurement I will update the answer to reflect this – thisismydesign Aug 17 '22 at 15:40
  • @thisismydesign Already tried, still each route change tracks page_view twice. I opened a thread https://stackoverflow.com/questions/73390801/react-router-5-google-analytics-4-page-view-tracks-twice?noredirect=1#comment129607889_73390801 – fabpico Aug 17 '22 at 15:45
34

Given that google analytics is loaded and initialised with a tracking id.

Here is a solution for react-router version 4 using the <Route> component to track page views.

<Route path="/" render={({location}) => {
  if (typeof window.ga === 'function') {
    window.ga('set', 'page', location.pathname + location.search);
    window.ga('send', 'pageview');
  }
  return null;
}} />

You simply render this component inside the <Router> (but not as a direct child of a <Switch>).

What happens is that whenever the location prop changes it causes a re-render of this component (not actually rendering anything) that fire a pageview.

heyhugo
  • 1,015
  • 9
  • 14
  • 1
    React-router 4. Is there anything it Cant Do?! – Anthony Cregan May 03 '17 at 11:56
  • 1
    Posted another another react-router-4 solution below that doesn't involve modifying individual routes. Sadly this is definitely a "choose your poison" type of situation. – Peter Berg May 17 '17 at 23:28
  • 1
    Doesn't this mean that going to "/" will render nothing? – Dana Woodman Oct 15 '17 at 20:16
  • 3
    Just have another route that emits whatever you want @DanaWoodman. This assumes the route isn't in a `Switch` – bozdoz Nov 20 '17 at 03:22
  • Will this track two pageviews on the landing page? Seeing as GA tracks the landing page automatically, and we trigger an extra pageview event for it. Or does GA filter it out? – ArneHugo May 17 '20 at 10:17
  • @ArneHugo No this should not track two pageviews. Make sure you don't track a pageview in your initialisation script as well. – heyhugo May 18 '20 at 13:37
  • 2
    This man deserves a nobel, we have tried in every way, but this is the only one who works without downsides. Thanks! – Danilo Cunha Oct 29 '20 at 20:10
32

I'm using React Router v4 and the Google Analytics Global Site Tag, which appears to be recommended at the time of writing this.

And here's my solution:

Create a component wrapped in withRouter from react-router-dom:

import React from 'react';
import { withRouter } from 'react-router-dom';
import { GA_TRACKING_ID } from '../config';

class GoogleAnalytics extends React.Component {
    componentWillUpdate ({ location, history }) {
        const gtag = window.gtag;

        if (location.pathname === this.props.location.pathname) {
            // don't log identical link clicks (nav links likely)
            return;
        }

        if (history.action === 'PUSH' &&
            typeof(gtag) === 'function') {
            gtag('config', GA_TRACKING_ID, {
                'page_title': document.title,
                'page_location': window.location.href,
                'page_path': location.pathname
            });
        }
    }

    render () {
        return null;
    }
}

export default withRouter(GoogleAnalytics);

Simply add the component within your router (I believe ideally after any routes that would be matched and any Switch components, because the analytics function should not be priority over your site rendering):

import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import IndexPage from './IndexPage';
import NotFoundPage from './NotFoundPage';
import GoogleAnalytics from './GoogleAnalytics';

const App = () => (
    <Router>
        <Switch>
            <Route exact path="/" component={IndexPage} />
            <Route component={NotFoundPage} />
        </Switch>
        <GoogleAnalytics />
    </Router>
);

As stated:

withRouter will re-render its component every time the route changes with the same props as render props

So when the route changes, the GoogleAnalytics component will update, it will receive the new location as props, and history.action will be either PUSH for a new history item or POP to signal going backwards through the history (which I think shouldn't trigger a page view, but you can adjust the if statements in componentWillUpdate as you see fit (you could even try componentDidUpdate with this.props instead, but I'm unsure which is better)).

bozdoz
  • 12,550
  • 7
  • 67
  • 96
  • bozdoz how did you add Global Site Tag to your page. Did you just add to your html page below the body tag ? – me-me Dec 01 '17 at 19:58
  • 1
    @me-me Yes. But within the body tag: ` ... ` – bozdoz Dec 01 '17 at 22:47
  • A few tweaks were needed for the latest React and React Router. Change `componentWillMount` to `componentDidMount`. Change the `page_path` to `this.props.location.pathname`. Wrap the Switch and GoogleAnalytics components in a
    – mjhm May 03 '18 at 23:25
  • Not sure where you're seeing `componentWillMount`, and not sure how `page_path` differs, but I would try wrapping Switch and GA component in `` instead of a `div`. Thanks! – bozdoz May 04 '18 at 17:58
  • This is not accurate. A pageview is not just a history.action of push. If this is used then the definition of pageview is being customized. And then when comparing your site to say, an industry benchmark, you are comparing apples to oranges. Because your definition of a pageview is non-standard. Standard definition includes actions like `pop` from back button etc. See standard definition from Google here: https://support.google.com/analytics/answer/1257084?hl=en – Joshua Robinson Sep 12 '18 at 17:20
  • 2
    Hey @JoshuaRobinson, I wrote at the bottom, "...I think shouldn't trigger a page view, but you can adjust...". This question was about integrating Google Analytics with React Router, not about which views you should log. Having said that, I may adjust my component, seeing as Google will track it differently on their end. Thanks. – bozdoz Sep 12 '18 at 18:40
  • @bozdoz Was adressing the answer above not the question. Google makes a distinction between a unique page view and a page view, can apply a filter to see this data. Cheers – Joshua Robinson Sep 12 '18 at 19:05
  • Do you have a solution to trigger on page load ? Because this is only working when the history is changing. – Kaherdin Feb 15 '19 at 14:17
  • Remove `history.action === 'push'`, or probably better is to add a componentDidMount with the same gtag script in there. – bozdoz Feb 15 '19 at 15:12
20

Note if you're using the react-router-dom package from react-router-4 you can handle this like so:

import { Router, Route } from 'react-router-dom';
import { createBrowserHistory } from 'history';

const history = createBrowserHistory();
const initGA = (history) => {
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'YOUR_IDENTIFIER_HERE', 'auto');
  ga('send', 'pageview');

  history.listen((location) => {
    console.log("tracking page view: " + location.pathname);
    ga('send', 'pageview', location.pathname);
  });
};

initGA(history);

class App extends Component { //eslint-disable-line
  render() {
    return
      (<Router history={history} >
         <Route exact path="/x" component={x} />
         <Route exact path="/y" component={y} />
       </Router>)
  }
}

Note that this requires you to install the history package (npm install history). This is already a dependency of react-router-dom so you're not adding any page weight here.

Also note: It is not possible to use the BrowserRouter component AND instrument your ga tracking this way. This is okay because the BrowserRouter component is just a really thin wrapper around the Router object. We recreate the BrowserRouter functionality here with <Router history={history}> where const history = createBrowserHistory();.

Samir K
  • 91
  • 2
  • 11
Peter Berg
  • 6,006
  • 8
  • 37
  • 51
  • 1
    you never call initGA? – Muhammad Umer Jul 10 '17 at 16:12
  • @MuhammadUmer true, just fixed it – Peter Berg Jul 11 '17 at 17:23
  • Why don't you just add the GA in your static HTML? I plus 1'd you. Because I think listening to the history object is the correct way to go. – Vince V. Feb 01 '18 at 15:42
  • @VinceV. You could conceivably initialize the `history` object within your build and then store history on the `window` object and access it in a script tag in your `` but I think that'd ultimately end up making your build pipeline more complicated. ¯\_(ツ)_/¯ – Peter Berg Feb 01 '18 at 21:23
  • If you are using the `BrowserRouter` component see the answer below that offers an alternative solution. – Toshe Feb 03 '18 at 16:33
  • This is the simplest answer that worked here. Thanks! – vmf91 Feb 05 '19 at 00:17
18

I would suggest using the excellent react-router-ga package that is extremely lightweight and easy to configure, especially when using the BrowserRouter wrapper.

Import the component:

import Analytics from 'react-router-ga';

Then simply add the <Analytics> within your BrowserRouter:

<BrowserRouter>
    <Analytics id="UA-ANALYTICS-1">
        <Switch>
            <Route path="/somewhere" component={SomeComponent}/>
        </Switch>
    </Analytics>
</BrowserRouter>
Sandro
  • 491
  • 7
  • 20
Toshe
  • 766
  • 4
  • 15
12

I like how Mark Thomas Müller suggests here:

In your index.js

import ReactGA from 'react-ga'

ReactGA.initialize('YourAnalyticsID')

ReactDOM.render(<App />, document.getElementById('root'))

Where your routes are:

import React, { Component } from 'react'
import { Router, Route } from 'react-router-dom'
import createHistory from 'history/createBrowserHistory'
import ReactGA from 'react-ga'

const history = createHistory()
history.listen(location => {
    ReactGA.set({ page: location.pathname })
    ReactGA.pageview(location.pathname)
})

export default class AppRoutes extends Component {
    componentDidMount() {
        ReactGA.pageview(window.location.pathname)
    }

    render() {
        return (
            <Router history={history}>
                <div>
                    <Route path="/your" component={Your} />
                    <Route path="/pages" component={Pages} />
                    <Route path="/here" component={Here} />
                </div>
            </Router>
        )
    }
}

Short, scalable and simple :)

Jöcker
  • 5,281
  • 2
  • 38
  • 44
6

Always go with the library's recommended way

In the React-GA documentation, they have added a community component recommended for using with React Router: https://github.com/react-ga/react-ga/wiki/React-Router-v4-withTracker

Implementation

import withTracker from './withTracker';

ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <Route component={withTracker(App, { /* additional attributes */ } )} />
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root'),
);

Code

import React, { Component, } from "react";
import GoogleAnalytics from "react-ga";

GoogleAnalytics.initialize("UA-0000000-0");

const withTracker = (WrappedComponent, options = {}) => {
  const trackPage = page => {
    GoogleAnalytics.set({
      page,
      ...options,
    });
    GoogleAnalytics.pageview(page);
  };

  // eslint-disable-next-line
  const HOC = class extends Component {
    componentDidMount() {
      // eslint-disable-next-line
      const page = this.props.location.pathname + this.props.location.search;
      trackPage(page);
    }

    componentDidUpdate(prevProps) {
      const currentPage =
        prevProps.location.pathname + prevProps.location.search;
      const nextPage =
        this.props.location.pathname + this.props.location.search;

      if (currentPage !== nextPage) {
        trackPage(nextPage);
      }
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };

  return HOC;
};

export default withTracker;
Paras
  • 9,258
  • 31
  • 55
3

here is a simplest way to track all paths with some work arounds:

npm i --save history react-ga

create a file history.js

import { createBrowserHistory } from "history"
import ReactGA from "react-ga"

ReactGA.initialize(process.env.REACT_APP_GA)

const history = createBrowserHistory()
history.listen((location) => {
    ReactGA.pageview(location.pathname)
})

// workaround for initial visit
if (window.performance && (performance.navigation.type === performance.navigation.TYPE_NAVIGATE)) {
    ReactGA.pageview("/")
}

export default history

and then import it to where is set your Router

import history from "./history"

...

class Route extends Component {
render() {
    return (
        <Router history={history}>
            <Switch>
              <Route path="/" exact component={HomePage} />
              ...
            </Switch>
        </Router>
    )
}

export default Route

References:

Gustavo Gonzalez | medium.com

History | GitHub

fsilva
  • 406
  • 1
  • 3
  • 9
2

First, in your index.js set onUpdate function to call ga

import ga from 'ga.js';
onUpdate() {
  console.log('=====GA=====>', location.pathname);
  console.log('=====GA_TRACKING_CODE=====>', GA_TRACKING_CODE);
  ga("send", "pageview", location.pathname);
}

render() {
  return (
    <Router onUpdate={this.onUpdate.bind(this)}>...</Router>
  );
}

And ga.js:

'use strict';
if(typeof window !== 'undefined' && typeof GA_TRACKING_CODE !== 'undefined') {
  (function(window, document, script, url, r, tag, firstScriptTag) {
    window['GoogleAnalyticsObject']=r;
    window[r] = window[r] || function() {
      (window[r].q = window[r].q || []).push(arguments)
    };
    window[r].l = 1*new Date();
    tag = document.createElement(script),
    firstScriptTag = document.getElementsByTagName(script)[0];
    tag.async = 1;
    tag.src = url;
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
  })(
    window,
    document,
    'script',
    '//www.google-analytics.com/analytics.js',
    'ga'
  );

  var ga = window.ga;

  ga('create', GA_TRACKING_CODE, 'auto');

  module.exports = function() {
    return window.ga.apply(window.ga, arguments);
  };
} else {
  module.exports = function() {console.log(arguments)};
}
Mogsdad
  • 44,709
  • 21
  • 151
  • 275
WeiYuan
  • 5,922
  • 2
  • 16
  • 22
2

I suggest using the Segment analytics library and following the React quickstart guide to track page calls using the react-router library. You can allow the <Route /> component to handle when the page renders and use componentDidMount to invoke page calls. The example below shows one way you could do this:

    const App = () => (
      <div>
        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/about" component={About} />
        </Switch>
      </div>
    );

    export default App;
    export default class Home extends Component {
      componentDidMount() {
        window.analytics.page('Home');
      }

      render() {
        return (
          <h1>
            Home page.
          </h1>
        );
      }
    }

I’m the maintainer of https://github.com/segmentio/analytics-react. With Segment, you’ll be able to switch different destinations on-and-off by the flip of a switch if you are interested in trying multiple analytics tools (we support over 250+ destinations) without having to write any additional code.

William
  • 87
  • 4
1

If you use hash or browser history you can do:

import trackingHit from 'tracking';

import { Router, browserHistory } from 'react-router';
browserHistory.listen(trackingHit);
// OR
import { Router, hashHistory } from 'react-router';
hashHistory.listen(trackingHit);

where ./tracking.es6

export default function(location) {
    console.log('New page hit', location.pathname);
    // Do your shizzle here
}
sidonaldson
  • 24,431
  • 10
  • 56
  • 61
0

basic react-ga implementation with your index.js

var ReactGA = require('react-ga'); // require the react-ga module
ReactGA.initialize('Your-UA-ID-HERE'); // add your UA code 

function logPageView() { // add this function to your component
  ReactGA.set({ page: window.location.pathname + window.location.search });
  ReactGA.pageview(window.location.pathname + window.location.search);
}

React.render((
<Router history={createBrowserHistory()} onUpdate={logPageView} > // insert onUpdate props here
    <Route path="/" component={App}>
        <IndexRoute component={Home} onLeave={closeHeader}/>
        <Route path="/about" component={About} onLeave={closeHeader}/>
        <Route path="/gallery" component={Gallery} onLeave={closeHeader}/>
        <Route path="/contact-us" component={Contact} onLeave={closeHeader}>
            <Route path="/contact-us/:service" component={Contact} onLeave={closeHeader}/>
        </Route>
        <Route path="/privacy-policy" component={PrivacyPolicy} onLeave={closeHeader} />
        <Route path="/feedback" component={Feedback} onLeave={closeHeader} />
    </Route>
    <Route path="*" component={NoMatch} onLeave={closeHeader} />
</Router>), document.getElementById('root'));
Isaac Pak
  • 4,467
  • 3
  • 42
  • 48
  • @BigDong I don't know what closeHeader is. You would have to ask the OP that question because the render code is his. I am just showing how you would implement react-ga for his code (look for my // comments) – Isaac Pak Sep 17 '17 at 13:44
0

Based on @david-l-walsh and @bozdoz suggestions

I created a HOC that execute the window.ga('set','page','{currentUrl}) and window.ga('send', 'pageview'); function and is easly used directly in the router page...

this is the HOC:

import React from 'react';
import { history } from '../../store'; // or wherever you createBrowserHistory(); invokation is

function withGAHistoryTrack(WrappedComponent) {
  return class extends React.Component {
    constructor(props) {
      super(props);
    }

    componentDidMount() {
      const { location } = history;
      const page = location.pathname + location.search;

      if (typeof window.ga === 'function') {
        window.ga('set', 'page', page);
        window.ga('send', 'pageview');
      }
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

export default withGAHistoryTrack;

and is used this way in the router page:

<Route
 path={'yourPath'}
 component={withGAHistoryTrack(yourComponent)}
 exact
/>
Yuri Scarbaci
  • 1,475
  • 2
  • 11
  • 13
0

For dynamically updating url on some event (like onClick etc), following can be used:

 //Imports
 import ReactGA from "react-ga";
 import { createBrowserHistory } from "history";

 // Add following on some event, like onClick (depends on your requirement)
 const history = createBrowserHistory();
 ReactGA.initialize("<Your-UA-ID-HERE>");
 ReactGA.pageview(history.location.pathname);
Santosh Pillai
  • 8,169
  • 1
  • 31
  • 27