Jacob Hyde
Jacob Hyde

Reputation: 1000

Facebook Pixel with Conversion API - Help tracking same event ids

I have run into a bit a frustrating problem with duplicating the "PageView" event for my pixel in my Nuxt.js (Vue) project with my Facebook Conversion API.

I initially stripped out this packages code, so that I could modify to include a eventID for every "PageView" event: https://github.com/WilliamDASILVA/nuxt-facebook-pixel-module#readme

My "PageView" event workflow is like this:

  1. On Vue router "afterEach" hook, call the tracking function.
  2. When I call my tracking function, make a async call to my API, which will fire off the server Conversion API with the "PageView" event. The API call returns the exact same eventID, so we can duplicate on the FE.
  3. Call Facebooks Pixel track method with the given eventID.

Initial load works fine:

Duplicated PageView Event

However, subsequent calls after a route change does not work. Both the server level event and the browser level event are being tracked, but not duplicated.

Not Duplicated after vue router change

First thing I noticed was there was no eventID being set for the browser call. I spent hours ensuring that the correct function was being called to the facebook pixel in the browser with the eventID, it is.

I found https://gist.github.com/sunderls/dfd5293a8b8f24a4ef37189a1d8c1b46 online, and began to try to figure out what was happening. It looks like an event handler is automatically added to the pixel when the replacestate/pushstate of the browsers history is called. This would explain why no eventID is being passed along, it's firing automatically.

Looking through Facebooks documentation I found that I could set:

fbq.disablePushState = true

Which would then stop the pixel from adding those event listeners. However, when I add this line of code above, I do not receive any subsequent "PageView" events after the initial page load, only server events are coming through.

Here is my plugin source code:

import { Minimatch } from "minimatch"
import axios from "axios"
import cookie from "js-cookie"

/**
 * @class Fb
 */
class Fb {
  constructor (fbq, options, eventUrl) {
    this.options = options
    this.fbq = fbq
    this.eventUrl = eventUrl

    this.isEnabled = !options.disabled
  }

  setPixelId (pixelId) {
    this.options.pixelId = pixelId
    this.init()
  }

  /**
   * @method enable
   */
  enable () {
    this.isEnabled = true
    this.init()
    this.track()
  }

  /**
   * @method disable
   */
  disable () {
    this.isEnabled = false
  }

  /**
   * @method init
   */
  init () {
    this.query('init', this.options.pixelId)
  }

  /**
   * @method track
   */
  async track (event = null, parameters = null, eventId = null) {
    if (!event) {
      event = this.options.track
    }

    if (this.eventUrl && !eventId) {
      if (cookie.get("auth._token.cookie")) {
        axios.defaults.headers.common["Authorization"] = cookie.get("auth._token.cookie");
      }
      try {
        let url = this.eventUrl
        const queryString = []
        if (event === "PageView") {
          queryString.push("url=" + window.location.href.split('?')[0])
          queryString.push("view=true")
        }
        if (cookie.get("_fbp")) {
          queryString.push(`_fbp=${cookie.get("_fbp")}`)
        }
        if (cookie.get("_fbc")) {
          queryString.push(`_fbc=${cookie.get("_fbc")}`)
        }
        if (queryString.length !== 0) {
          url += "?" + queryString.join("&")
        }
        const {data: {data}} = await axios.get(url)
        eventId = data.event_id
      } catch (err) {
        console.error(err)
      }
    }

    this.query('track', event, parameters, eventId)
  }

  /**
   * @method query
   * @param {string} cmd
   * @param {object} option
   * @param {object} parameters
   */
  query (cmd, option, parameters = null, eventId = null) {
    if (this.options.debug) log('Command:', cmd, 'Option:', option, 'Additional parameters:', parameters, 'Event Id:', eventId)
    if (!this.isEnabled) return

    if (!parameters && !eventId) {
      this.fbq(cmd, option)
    } else if (parameters && !eventId) {
      this.fbq(cmd, option, parameters)
    } else {
      this.fbq(cmd, option, {}, {eventID: eventId})
    }
  }
}

function getMatchingPixel (options, path) {
  return options.pixels.find(pixel => {
    const routeIndex = pixel.routes.findIndex(route => {
      const minimatch = new Minimatch(route)
      return minimatch.match(path)
    })

    return routeIndex !== -1
  })
}

function log (...messages) {
  console.info.apply(this, ['[nuxt-facebook-pixel-module]', ...messages])
}

export default (ctx, inject) => {
  let _fbq
  const parsedOptions = <%= JSON.stringify(options) %>
  const isDev = parsedOptions.dev && !parsedOptions.debug

  if (isDev) log('You are running in development mode. Set "debug: true" in your nuxt.config.js if you would like to trigger tracking events in local.')

  const { path } = ctx.route
  const matchingPixel = getMatchingPixel(parsedOptions, path)

  const pixelOptions = Object.assign({}, matchingPixel || parsedOptions)

  /* eslint-disable */
  if (typeof window !== 'undefined') {
    ((f, b, e, v, n, t, s) => {
      if (f.fbq) return; n = f.fbq = function () {
        n.callMethod ?
          n.callMethod.apply(n, arguments) : n.queue.push(arguments)
      };
      if (!f._fbq) f._fbq = n; n.push = n; n.loaded = !0; n.version = pixelOptions.version;
      n.queue = [];
      t = b.createElement(e);
      t.async = true;
      t.defer = true;
      t.src = v;
      s = b.getElementsByTagName('body')[0];
      s.parentNode.appendChild(t, s);

      _fbq = fbq;

      if (!isDev && !pixelOptions.disabled) {
        if (pixelOptions.manualMode) {
          fbq('set', 'autoConfig', false, pixelOptions.pixelId)
        }
        // fbq.disablePushState = true
        fbq('init', pixelOptions.pixelId)
        // fbq('track', pixelOptions.track)
      }
    })(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');
  }
  /* eslint-enable */

  const instance = new Fb(_fbq, pixelOptions, parsedOptions.eventUrl)

  if (ctx.app && ctx.app.router) {
    const router = ctx.app.router
    router.afterEach(({ path }) => {
      /**
       * Change the current pixelId according to the route.
       */
      const matchingPixel = getMatchingPixel(parsedOptions, path)

      const pixelOptions = Object.assign({}, matchingPixel || parsedOptions)
      if (pixelOptions.pixelId !== instance.options.pixelId) {
        instance.setPixelId(pixelOptions.pixelId)
      }

      /**
       * Automatically track PageView
       */
      if (parsedOptions.autoPageView) {
        instance.track('PageView')
      }
    })
  }

  /* eslint-enable */
  ctx.$fb = instance
  inject('fb', instance)
}

All settings are correctly set for the plugin to work. I am not using multiple Facebook Pixels.

I would greatly appreciate help in figuring this out.

Upvotes: 2

Views: 3901

Answers (1)

Jacob Hyde
Jacob Hyde

Reputation: 1000

I got it working by setting:

fbq.disablePushState = true
fbq.allowDuplicatePageViews = true

Looking at the prettified Pixel code from: https://gist.github.com/sunderls/dfd5293a8b8f24a4ef37189a1d8c1b46

I found that within the track function there is this:

if (isPageview && wa.allowDuplicatePageViews === false && ca[methodCall.id] === true) continue;

So for some reason, allowing duplicate page views worked.

Upvotes: 5

Related Questions