Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding GA script tag? #160

Closed
robinvdvleuten opened this issue Oct 30, 2016 · 51 comments

Comments

Projects
None yet
@robinvdvleuten
Copy link

commented Oct 30, 2016

Hi there!

I am currently converting my website to a next.js project and can't figure out on how to add the Google Analytics script tag to the page. Any thoughts?

Cheers,
Robin

@nodegin

This comment has been minimized.

Copy link
Contributor

commented Oct 30, 2016

checkout <Head>

@pauldariye

This comment has been minimized.

Copy link

commented Oct 31, 2016

@robinvdvleuten I'd suggest creating a GoogleTagManager component and including it in the Head or right after it depending on your implementation choice.

@impronunciable

This comment has been minimized.

Copy link
Contributor

commented Nov 2, 2016

as people mentioned you can use the Head tag or a react-friendly solution like https://github.com/react-ga/react-ga

@gtramontina

This comment has been minimized.

Copy link

commented Nov 21, 2016

Any tips on getting react-ga working with next.js?

@impronunciable

This comment has been minimized.

Copy link
Contributor

commented Nov 22, 2016

@gtramontina it should work. Otherwise I'm one of the react-ga contributors so I could help with that

@musemind

This comment has been minimized.

Copy link

commented Dec 13, 2016

No need for react-ga. Use the google tag manager with the <Head> component to inject analytics. There are some small changes to the google tag manager code necessary to make it work in nextjs/react:

<Head>
      <script dangerouslySetInnerHTML={{__html: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
        new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
        j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
        'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
      })(window,document,'script','dataLayer','GTM-XXXXXX');`}} />
    </Head>
    <noscript dangerouslySetInnerHTML={{__html: `<iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX" height="0" width="0" style="display:none;visibility:hidden;"></iframe>`}} />

image

@MoOx

This comment has been minimized.

Copy link

commented Dec 13, 2016

I would recommend a better solution to track client side page view: autotrack. See phenomic/phenomic#384 for more information.

@sedubois

This comment has been minimized.

Copy link
Contributor

commented Jan 17, 2017

@impronunciable it would be great to have an example as after some googling I'm still confused 😃 Could this be reopened?

  • @MoOx seems to imply that react-ga isn't necessary, should use Google AutoTrack instead
  • but Autotrack instructs to create a script tag with code which accesses the window object, so that doesn't work server-side
  • and anyway if using a react component such as react-ga, where should it be put? In a render? componentWillMount? getInitialProps? It's not super clear as it kind of supposes that you use react-router and load the code a single time for all pages.

The page tracking should work both when the page is server-rendered and then when navigating to another page. It would be would be great to see how the whole thing should integrate in a typical Next app with a Head, a page layout HOC, etc.

Would also be great to know if you think there's a better/simpler/less cumbersome/more state-of-the-art combination than Next.js + Google Analytics Autotrack...

@impronunciable

This comment has been minimized.

Copy link
Contributor

commented Jan 17, 2017

@MoOx

This comment has been minimized.

Copy link

commented Jan 17, 2017

Pretty sure autotrack is more than enough, only injected on the client side - no need to think about the server-side.

@MoOx

This comment has been minimized.

Copy link

commented Jan 17, 2017

See MoOx/react-toulouse@c42045d for an example. It's not using next, but I guess it should be easy to adapt.

@timneutkens

This comment has been minimized.

Copy link
Member

commented Jan 17, 2017

Yeah we don't want to track server renders, Since it Will obfuscate things like location.

@MoOx

This comment has been minimized.

Copy link

commented Jan 17, 2017

No you don't since it will be handled by the client: keep in mind that a page is request by the client and will be rendered in the client browser :)

@sedubois

This comment has been minimized.

Copy link
Contributor

commented Jan 17, 2017

OK I didn't realise the code shouln't run server-side. So I used autotrack as recommended by @MoOx, I think this should work: sedubois/relate@50dc3f3

Just haven't confirmed yet if it counts page views correctly, this is new to me 😬 😄

@MoOx

This comment has been minimized.

Copy link

commented Jan 17, 2017

You can use "real time" view to debug GA easily.

@sedubois

This comment has been minimized.

Copy link
Contributor

commented Jan 17, 2017

@MoOx yes well that's what bothers me, I don't see anything in the realtime panel when I visit https://relate.now.sh in parallel in another tab (0 active users)

@sedubois

This comment has been minimized.

Copy link
Contributor

commented Jan 18, 2017

I'm still not getting this... I don't get any data with the code I wrote.

All the examples I see seem to require a <script href="https://www.google-analytics.com/analytics.js" async /> but things in the Head don't seem to be executed in the client? (my console.log aren't printed in browser console...)

An example would still be very appreciated 🙂

@sedubois

This comment has been minimized.

Copy link
Contributor

commented Jan 18, 2017

Ah, got it working by switching to react-ga 😄

sedubois/relate@a9d2bce

screen shot 2017-01-18 at 14 01 28

@luandro

This comment has been minimized.

Copy link

commented Mar 7, 2017

How did you get it to work? I've been trying to call a pageview function on componentDidMount.

The production app GA Check fails.

@sedubois

This comment has been minimized.

Copy link
Contributor

commented Mar 7, 2017

@luandro don't remember the details, but you can look at the commit I linked above if it helps

@luandro

This comment has been minimized.

Copy link

commented Mar 7, 2017

I did and tried it. I did the same as you, calling configureAnalytics on top, and pageview on componentWillMount, but still get negative on GA Check.

In your code it doesn't seem you actually connected page hoc anywhere, did you?

@durk0

This comment has been minimized.

Copy link

commented Apr 10, 2017

@luandro Simply add ga(‘set’, ‘page’, window.location.pathname); ga(‘send’, ‘pageview’); to change url of pageview event.

@ryardley

This comment has been minimized.

Copy link

commented May 3, 2017

Maybe there is a more 'next.js' way to do this but WRT running scripts on the page I found that if I created a Script component like so:

// modules/Script.js
export default ({children}) => (
  <script dangerouslySetInnerHTML={{__html: `(${children.toString()})();` }}></script>
);

Then I could do this which worked for me:

import Document, { Head, Main, NextScript } from 'next/document'
import Script from '../modules/Script';

export default class MyDocument extends Document {

  render () {
    return (
      <html>
        <body>
          <Main />
          <Script>
            {
              () => {
                console.log('foo');
              }
            }
          </Script>
          <NextScript />
        </body>
      </html>
    )
  }
}

The caveat is that you must enclose everything in a function.

@karthikiyengar

This comment has been minimized.

Copy link

commented May 17, 2017

Is it possible to track individual pageviews without explicitly calling the ga('send', 'pageview') on every page. If we absolutely have to do it, where do we invoke it from having certainty that ga has been loaded?

Is it possible to tie into route change events in some way to log pageviews?

@vinaypuppal

This comment has been minimized.

Copy link
Contributor

commented May 17, 2017

@karthikiyengar I am using react-ga as suggested by others above and logging pageviews on componentDidMount of every page.

import ReactGA from 'react-ga'

export const initGA = () => {
  console.log('GA init')
  ReactGA.initialize('UA-xxxxxxxx-x')
}
export const logPageView = () => {
  ReactGA.set({ page: window.location.pathname })
  ReactGA.pageview(window.location.pathname)
}
componentDidMount () {
    initGA()
    logPageView()
  }
@youfoundron

This comment has been minimized.

Copy link

commented May 29, 2017

@vinaypuppal is it common practice to initGA for every page component?

@notrab

This comment has been minimized.

Copy link
Contributor

commented May 31, 2017

@rongierlach I'm using a Layout component which calls the initialize and pageview methods on ReactGA within componentDidMount().

export default class extends Component {
  componentDidMount() {
    ReactGA.initialize('UA-1234567-1')
    ReactGA.pageview(document.location.pathname)
  }

  render() {
    return (
      <div>
        {this.props.children}
     </div>
    )
}

This is the cleanest way I've done it yet as if the page is server rendered, it needs to initialize. I'm sure there is a hook that could bypass the initialize if it's client rendered.

@kylewiedman

This comment has been minimized.

Copy link

commented Jun 9, 2017

@notrab couldn't you potentially be missing the shallow renders? They your app will just transition and your layout component may not remount. This is what I have to catch those.

componentDidMount() {
    ReactGA.initialize('xx-xxxxxxx-x')
    let trackMe = true

    if (trackMe) {
      ReactGA.pageview(document.location.pathname)
      trackMe = false
    }

    Router.onRouteChangeStart = () => {
      NProgress.start()
      this.store.dispatch(transitionPage(true))
      trackMe = true
    }
    Router.onRouteChangeComplete = () => {
      NProgress.done()
      this.store.dispatch(transitionPage(false))
      if (trackMe) { ReactGA.pageview(document.location.pathname) }
    }
    Router.onRouteChangeError = () => {
      NProgress.done()
      this.store.dispatch(transitionPage(false))
    }

    this.store.dispatch(calculateResponsiveState(window))
  }
@exogenesys

This comment has been minimized.

Copy link

commented Jun 17, 2017

@filostrato How did you fix it? I'm stuck at it as well. :)

@filostrato

This comment has been minimized.

Copy link

commented Jun 17, 2017

@exogenesys Ended up putting this in utils/analytics.js:

import ReactGA from 'react-ga'

const dev = process.env.NODE_ENV !== 'production'

export const initGA = () => {
  ReactGA.initialize("UA-xxxxxxxxx-x", {
    debug: dev,
  })
}

export const logPageView = (
  pageName = window.location.pathname + window.location.search
) => {
  ReactGA.set({page: pageName})
  ReactGA.pageview(pageName)
}

export const trackCustomEvent = (category, action) =>
  ReactGA.event({category, action})

export default undefined

and changed my Layout.js to extend React.Component:

export default class Layout extends React.Component {
    componentDidMount () {
      if (!window.GA_INITIALIZED) {
        initGA()
        window.GA_INITIALIZED = true
    }
    logPageView()
  }

  render () {
    return (
      <Main>
        <Header />
        {this.props.children}
      </Main>
    )
  }
}

Didn't find a way to make it work in a component which utilizes getInitialProps, but since Layout.js doesn't it worked just fine; not sure what to do if I want more detailed statistics, but I'll cross that bridge when I come to it.

@exogenesys

This comment has been minimized.

Copy link

commented Jun 18, 2017

@filostrato That did it. Thanks a lot. :)

@osartun

This comment has been minimized.

Copy link

commented Jun 25, 2017

Hey guys

I've just dealt with the implementation of GA on my site as well and this is what I've come up with.

First I used the solution from @notrab that seemed clean and straight forward:

export default class extends Component {
  componentDidMount() {
    ReactGA.initialize('UA-XXXXXXX-X')
    ReactGA.pageview(document.location.pathname)
  }

  render() {
    …
  }
}

However, I noticed:

  • componentDidMount was executed when I switched between pages of different types, so the initialize function was called several times
  • componentDidMount was only executed when I switched between pages of different types, so the pageview function was called only then => I have a video pagetype on my website and I saw in the GA Live view that while switching between different video pages only the initial video page was tracked

I created a higher order component to address these two problems. For the first one I used @filostrato's solution.

import React, { Component } from 'react';
import ReactGA from 'react-ga';
import Router from 'next/router';

const debug = process.env.NODE_ENV !== 'production';

export default (WrappedComponent) => (
  class GaWrapper extends Component {
    constructor (props) {
      super(props);
      this.trackPageview = this.trackPageview.bind(this);
    }

    componentDidMount() {
      this.initGa();
      this.trackPageview();
      Router.router.events.on('routeChangeComplete', this.trackPageview);
    }

    componentWillUnmount() {
      Router.router.events.off('routeChangeComplete', this.trackPageview);
    }

    trackPageview (path = document.location.pathname) {
      if (path !== this.lastTrackedPath) {
        ReactGA.pageview(path);
        this.lastTrackedPath = path;
      }
    }

    initGa () {
      if (!window.GA_INITIALIZED) {
        ReactGA.initialize('UA-XXXXXX-XX', { debug });
        window.GA_INITIALIZED = true;
      }
    }

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

And in your <Layout> / <PageWrapper> / <PageScaffold> component or whatever you call it:

import GaWrapper from './ga-wrapper';

const PageScaffold = () => (…);

export default GaWrapper(PageScaffold);

Maybe that helps anyone.

I don't like the call of Router.router.events.… though, because this isn't part of the documented API. However, when I tried to call Router.onRouteChangeComplete which should be the correct way, I got a ReferenceError because onRouteChangeComplete was undefined. Maybe the documentation is outdated?

@toolgirl

This comment has been minimized.

Copy link

commented Jul 27, 2017

This has worked for me in a next.js website. And got around the 'window not defined' error I was running into with the usual implementation of react-ga.

@florinpop17

This comment has been minimized.

Copy link

commented Aug 14, 2017

@osartun solution worked for me. Thank you! 👍

@jesstelford

This comment has been minimized.

Copy link
Contributor

commented Sep 18, 2017

Thanks to @osartun's code, I was able to come up with the following solution for firing pageview events using google's new gtag (which replaces the universal analytics scripts):

pages/_document.js

import Document, { Head } from 'next/document';

const GA_TRACKING_ID = '..';

export default class MyDocument extends Document {
  render() {
    return (
      <html lang="en-AU">
        <Head>
          <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>
        ...
      </html>
    );
  }
}

scaffold.js / layout.js / wraper.js / whatever your project names it:

import url from 'url';
import Router from 'next/router';

const GA_TRACKING_ID = '...';

const withPageViews = WrappedComponent =>
  class GaWrapper extends React.Component {
    componentDidMount() {
      // We want to do this code _once_ after the component has successfully
      // mounted in the browser only, so we use a special semiphore here.
      if (window.__NEXT_ROUTER_PAGEVIEW_REGISTERED__) {
        return;
      }

      window.__NEXT_ROUTER_PAGEVIEW_REGISTERED__ = true;
      let lastTrackedUrl = '';

      // NOTE: No corresponding `off` as we want this event listener to exist
      // for the entire lifecycle of the page
      // NOTE: This does _not_ fire on first page load. This is what we want
      // since GA already tracks a page view when the tag is first loaded.
      Router.router.events.on('routeChangeComplete', (newUrl = document.location) => {
        if (newUrl === lastTrackedUrl || !window.gtag) {
          return;
        }

        // Don't double track the same URL
        lastTrackedUrl = newUrl;

        // Believe it or not, this triggers a new pageview event!
        // https://developers.google.com/analytics/devguides/collection/gtagjs/single-page-applications
        window.gtag('config', GA_TRACKING_ID, {
          page_path: url.parse(newUrl).path,
        });
      });
    }

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

const PageScaffold = () => (…);

export default withPageViews(PageScaffold);
@trezy

This comment has been minimized.

Copy link

commented Sep 22, 2017

For anybody coming late to the party, the reason @kylewiedman's solution doesn't work is because the Router events are being defined inside of componentDidMount. Defining them outside of the component seems to fix the problem:

https://gist.github.com/trezy/e26cb7feb2349f585d2daf449411d0a4

@kachkaev

This comment has been minimized.

Copy link
Contributor

commented Oct 1, 2017

@trezy when using gtag, there seems to be no need to import ReactGA from 'react-ga'.

I used your <script dangerouslySetInnerHtml={...} /> example and just added this one line into the injection:

window.gaTrackingId = '${gaTrackingId}';

Afterwards, this lightweight trick began to work:

Router.onRouteChangeComplete = () => {
  if (window.gtag) {
    window.gtag('config', window.gaTrackingId, {
      page_location: window.location.href,
      page_path: window.location.pathname,
      page_title: window.document.title,
    });
  }
};

A pretty large analytics.js is no longer fetched, but all the pages are still tracked!

@nmaro

This comment has been minimized.

Copy link

commented Mar 1, 2018

Since this is not obvious, it would be good to have an example in the repo.

@timneutkens

This comment has been minimized.

Copy link
Member

commented Mar 2, 2018

Or a next-google-analytics repository 🕵️

@guigrpa

This comment has been minimized.

Copy link

commented May 4, 2018

@prichodko Thanks a lot for putting together this example!!

@aequasi

This comment has been minimized.

Copy link

commented Jul 12, 2018

@andylacko

This comment has been minimized.

Copy link

commented Nov 12, 2018

found pretty nice lib, added to main layout file in componentDidMount() and it seems to work correctly. It is well documented, no hustle

https://www.npmjs.com/package/react-gtm-module

@Vadorequest

This comment has been minimized.

Copy link

commented Nov 12, 2018

@andylacko Indeed, I had forked it to fix a few things, like HTTPS for instance: https://github.com/Vadorequest/react-gtm

@williamli

This comment has been minimized.

Copy link

commented Dec 10, 2018

Just adding my solution to this here in case it might be useful for someone later on.

I used react-ga but the same logic should work with other ga tools.

_app.js's getInitialProps is triggered on the client side whenever user click on a new route. (First time it will run on server side).

componentDidMount of _app.js runs on client side only.

So in my _app.js, I added the following few lines of code


static async getInitialProps({ Component, router, ctx }) {
    let pageProps = {}

    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx);      
    }

    // client-side only, run on page changes, do not run on server (SSR)
    if (typeof(window) === "object") {
      ReactGA.pageview(ctx.asPath);
    }
    return { pageProps, router }
  }
  
  
  componentDidMount() {
    // client-side only, run once on mount
    ReactGA.initialize('UA-XXXXXXX-3');
    ReactGA.pageview(window.location.pathname + window.location.search);
  }

When the page is rendered in server, nothing will happen in getInitialProps as I wrapped the GA code in typeof(window) === "object".

On client side, getInitialProps will not run for the first time since everything is server rendered. GA is setup inside componentDidMount on the client side.

Subsequent route changes (even for the same route), will trigger getInitialProps on client side and ga event is triggered.

@ChrisEdson

This comment has been minimized.

Copy link

commented Feb 28, 2019

Just adding my solution to this here in case it might be useful for someone later on.

I used react-ga but the same logic should work with other ga tools.

_app.js's getInitialProps is triggered on the client side whenever user click on a new route. (First time it will run on server side).

componentDidMount of _app.js runs on client side only.

So in my _app.js, I added the following few lines of code


static async getInitialProps({ Component, router, ctx }) {
    let pageProps = {}

    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx);      
    }

    // client-side only, run on page changes, do not run on server (SSR)
    if (typeof(window) === "object") {
      ReactGA.pageview(ctx.asPath);
    }
    return { pageProps, router }
  }
  
  
  componentDidMount() {
    // client-side only, run once on mount
    ReactGA.initialize('UA-XXXXXXX-3');
    ReactGA.pageview(window.location.pathname + window.location.search);
  }

When the page is rendered in server, nothing will happen in getInitialProps as I wrapped the GA code in typeof(window) === "object".

On client side, getInitialProps will not run for the first time since everything is server rendered. GA is setup inside componentDidMount on the client side.

Subsequent route changes (even for the same route), will trigger getInitialProps on client side and ga event is triggered.

Hey Will. Does including ReactGA in your webpack (as it's run clientside) have a significant effect on your build size?

@williamli

This comment has been minimized.

Copy link

commented Feb 28, 2019

@ChrisEdson https://github.com/react-ga/react-ga the whole library is 161kb zipped.

@ChrisEdson

This comment has been minimized.

Copy link

commented Mar 1, 2019

@williamli

This comment has been minimized.

Copy link

commented Mar 1, 2019

@ChrisEdson

This comment has been minimized.

Copy link

commented Mar 1, 2019

So the minified script is 15kb. That's quite small.

I'm going to bundle analyse my webpack and I'll report back on how much it adds in practice.

@ChrisEdson

This comment has been minimized.

Copy link

commented Mar 1, 2019

Within a total bundle size of ~500kb, my ReactGA adds about 27kb. So pretty small in the grand scheme of things.

ijjk pushed a commit to ijjk/next.js that referenced this issue Apr 13, 2019

Add new Travis CI guide to documentation (zeit#160)
* Add new Travis CI docs

* Implement feedback

Co-Authored-By: Timothy <hello@timothy.is>

* Improve Travis guide

* Fix weird spacing in Code blocks

* Better list order

* Further Travis guide improvements

* Give the first paragraph a context to Travis

* Better titles
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.