nfl / react-metrics

An analytics module for React
MIT License
615 stars 45 forks source link

Working example with NextJS #37

Open jdwinall-tm opened 7 years ago

jdwinall-tm commented 7 years ago

Do you have an example for a site that supports SSR? I am using NextJS and I am not able to limit importing the vendor/GoogleAnalytics.js class to only client side rendering. How do I replace the GoogleAnalyticsStub below to get this working?

metrics.config.js

import { ga } from 'client-config'
import GoogleAnalytics from '../lib/GoogleAnalytics'

const MetricsConfig = {
  vendors: [{
    name: 'Google Analytics',
    api: new GoogleAnalytics({
      trackingId: ga.tracking_id
    })
  }],
  pageViewEvent: 'pageLoad',
  pageDefaults: () => {
    return {
      environment: ga.environment,
      siteName: ga.siteName,
      timestamp: Date.now(),
      path: '/'
    }
  },
  debug: ga.debug
}

export default MetricsConfig

Layout.js

import React from 'react'
import pageLoadTracking from '../lib/pageLoadTracking'

const Layout = ({ children }) => (
  <Main>
    {children}
  </Main>
)

export default pageLoadTracking(Layout)

package.json

 "dependencies": {
    "above-the-fold-only-server-render": "^1.0.3",
    "analytics.js": "^2.9.1",
    "config": "^1.25.1",
    "express": "^4.15.2",
    "js-yaml": "^3.8.3",
    "mkdirp": "^0.5.1",
    "next": "^2.3.1",
    "next-routes": "^1.0.26",
    "prop-types": "^15.5.8",
    "react": "^15.5.4",
    "react-apollo": "^1.2.0",
    "react-dom": "^15.5.4",
    "react-ga": "^2.2.0",
    "react-gpt": "^0.2.4",
    "react-metrics": "^2.3.1",
    "react-no-ssr": "^1.1.0",
    "styled-components": "^1.4.5"
  },
  "devDependencies": {
    "babel-eslint": "^7.2.3",
    "babel-plugin-module-resolver": "^2.7.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-stage-0": "^6.24.1",
    "babel-register": "^6.24.1",
    "chai": "^3.5.0",
    "enzyme": "^2.8.2",
    "ignore-styles": "^5.0.1",
    "jsdom": "^9.12.0",
    "lighthouse": "^1.6.3",
    "mocha": "^3.2.0",
    "nightwatch": "^0.9.14",
    "phantomjs-prebuilt": "^2.1.14",
    "react-test-renderer": "^15.5.4",
    "selenium-server": "^3.3.1",
    "sinon": "^2.1.0",
    "standard": "^10.0.1"
  },

GoogleAnalytics.js

let analytics
if (process.browser) {
  analytics = require('analytics.js') // eslint-disable-line global-require
}

/**
 * Performs the tracking calls to Google Analytics.
 * Utilizing Segment IO Analytics Integration.
 *
 * @module GoogleAnalytics
 * @class
 * @internal
 */
class GoogleAnalytics {
  constructor (options = {}) {
    this.name = 'Google Analytics'
    this._loaded = false
    this.options = options
  }
  /**
   *
   * @method pageView
   * @param {String} eventName
   * @param {Object} params
   * @returns {Promise}
   * @internal
   */
  pageView (...args) {
    return this.track(...args)
  }
  user (userId) {
    return new Promise((resolve) => {
      this.userId = userId
      resolve({
        userId
      })
    })
  }
  /**
   *
   * @method track
   * @param {String} eventName
   * @param {Object} params
   * @returns {Promise}
   * @internal
   */
  track (eventName, params) {
    return new Promise((resolve, reject) => {
      this._load().then(() => {
        this._track(eventName, params)
        resolve({
          eventName,
          params
        })
      }).catch((error) => {
        console.error('GA: Failed to initialize', error)
        reject(error)
      })
    })
  }
  /**
   *
   * @method _track
   * @param {String} eventName
   * @param {Object} params
   * @protected
   */
  _track (eventName, params) {
    if (eventName === 'pageView') {
      analytics.page(params.category, params)
      return
    }
    analytics.track(eventName, params)
  }
  /**
   *
   * @method _load
   * @protected
   */
  _load () {
    return this._promise || (this._promise = new Promise((resolve) => {
      if (this._loaded) {
        resolve()
      } else {
        analytics.once('ready', () => {
          this._loaded = true
          resolve()
        })
        analytics.initialize({
          'Google Analytics': this.options
        })
      }
    }))
  }
}

export default GoogleAnalytics
jamsea commented 7 years ago

@jdwinall-tm Importing the library on the server side shouldn't cause an issue, as long as you make sure it's not firing any events on the server side.

I think this might solve your issue: https://github.com/nfl/react-metrics#override-default-page-view-tracking specifically this chunk of code:

    componentDidMount() {
        const {value1, value2} = this.props;
        this.context.metrics.pageView({value1, value2});
    }

Placing the logic in componentDidMount will force react to run it on the client side instead of the server side. Let me know if you need any more info!

jdwinall-tm commented 7 years ago

@jamsea Is there a way to implement this on a component that is not a "route handling" component?

My issues seems to be with GoogleAnalytics.js. The first line of this file includes analytics.js, which imports analytics.js on the server and the client.

jdwinall-tm commented 7 years ago

@jamsea I updated the issue description to include my GoogleAnalytics.js file. Adding the following lines addressed my client errors on the server issue.

let analytics
if (process.browser) {
  analytics = require('analytics.js') // eslint-disable-line global-require
}

My only remaining issue is related to sending the active page when navigating between links with NextJS. When I view the real time content activity while browsing my dev site, my entry page reports correctly. However, when I click a link in the site, the page title updates but the active page stays the same (whatever the entry point to the application was).