react-metrics icon indicating copy to clipboard operation
react-metrics copied to clipboard

Working example with NextJS

Open jdwinall-tm opened this issue 8 years ago • 3 comments

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

jdwinall-tm avatar May 01 '17 18:05 jdwinall-tm

@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!

jamsea avatar May 11 '17 23:05 jamsea

@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 avatar May 24 '17 16:05 jdwinall-tm

@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).

jdwinall-tm avatar May 31 '17 20:05 jdwinall-tm