react-facebook-pixel icon indicating copy to clipboard operation
react-facebook-pixel copied to clipboard

How to use this library with next.js

Open icecold21 opened this issue 4 years ago • 24 comments

Hi, i was wondering how to use this library with next.js.

icecold21 avatar Jul 22 '20 07:07 icecold21

I am wondering the same.

charinduedirisuriya avatar Aug 04 '20 17:08 charinduedirisuriya

With the newest version, the only way I've found that works is to use a dynamic import because they are using window directly, and that doesn't work on server-side render, so it won't build.


// in your _app.tsx
import NextApp from 'next/app';
import Router from 'next/router';

export default class App extends NextApp {
  componentDidMount() {
    import('react-facebook-pixel')
      .then((x) => x.default)
      .then((ReactPixel) => {
        ReactPixel.init('your-pixel-id');
        ReactPixel.pageView();

        Router.events.on('routeChangeComplete', () => {
          ReactPixel.pageView();
        });
      });
  }
}

lineape avatar Aug 12 '20 03:08 lineape

Here's what I'm doing in a PixelProvider component that I wrap the rest of the app with in _app.tsx. Seems to work without any issues.

import React, { FC, useEffect } from "react";
import { useRouter } from "next/router";

const PixelProvider: FC = ({ children }) => {
  const router = useRouter();
  useEffect(() => {
    const shouldTrack =
      !isLocal() && !isDev() && isBrowser() && !window.FB_INITIALIZED;

    if (shouldTrack) {
      import("react-facebook-pixel")
        .then((module) => module.default)
        .then((ReactPixel) => {
          ReactPixel.init("3208084489267232");
          ReactPixel.pageView();
          router.events.on("routeChangeComplete", () => {
            ReactPixel.pageView();
          });
        });
    }
  }, []);

  return <>{children}</>;
};

export default PixelProvider;

const isBrowser = () => typeof window !== "undefined";

const isLocal = () => location.hostname === "localhost";

const isDev = () => process.env.NODE_ENV !== "production";

Hope this helps.

fikip avatar Aug 27 '20 11:08 fikip

I've made a little helper component that I place in my <App> component.

function FacebookPixel() {
  React.useEffect(() => {
    import("react-facebook-pixel")
      .then((x) => x.default)
      .then((ReactPixel) => {
        ReactPixel.init(constants.siteMeta.FacebookPixelID);
        ReactPixel.pageView();

        Router.events.on("routeChangeComplete", () => {
          ReactPixel.pageView();
        });
      });
  });
  return null;
}


export default function App({ Component, pageProps }) {
  return (
    <>
      <Head>
        <meta charSet="UTF-8" />
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1, viewport-fit=cover"
        />
      </Head>
      <FacebookPixel />
      //…
      <main className="routesContainer">
        <Component siteData={siteData} {...pageProps} />
      </main>
      //…
    </>
  );
}

ScottSmith95 avatar Aug 27 '20 21:08 ScottSmith95

I tried the samples above but they really don't work. The library should accept an ssr option to control if it's immediately invoked or not. This forces us to use the 1 year old buggy version of 0.1.3

omar-dulaimi avatar Sep 21 '20 17:09 omar-dulaimi

These were great suggestions but I noticed that during dev the router event listener isn't being destroyed so each time you make a code change it just stacks event bindings (you can see this if you add console logs to the code).

I ended up with this:

import React, { useEffect } from 'react'
import { useRouter } from 'next/router'

const FACEBOOK_PIXEL_ID = process.env.NEXT_PUBLIC_FACEBOOK_PIXEL_ID

export const FacebookPixel = function () {
  const router = useRouter()
  useEffect(() => {
    if (!FACEBOOK_PIXEL_ID) return
    let fb

    function onRouteChange(url) {
      fb.pageView()
    }

    import('react-facebook-pixel')
      .then(module => (fb = module.default))
      .then(() => {
        fb.init(FACEBOOK_PIXEL_ID, {
          autoConfig: true,
          debug: true,
        })
        fb.pageView()
      })

    router.events.on('routeChangeComplete', onRouteChange)
    return () => {
      router.events.off('routeChangeComplete', onRouteChange)
    }
  }, [])
  return null
}

It's not perfect because there's a chance the router event triggers before the module loads but I couldn't reproduce it so kept it simple.

@omar-dulaimi this works absolutely fine and I suspect you took a wrong turn somewhere if this didn't work out for you.

ashconnell avatar Sep 24 '20 22:09 ashconnell

Great solutions worked for me! If you're using next you should have access to async / await which makes this syntax a bit nicer imho:

export const trackFbPageView = async () => {
  const { default: ReactPixel } = await import('react-facebook-pixel');
  ReactPixel.init(FB_PIXEL_ID, advancedMatching, options);
  ReactPixel.pageView();
};

vincentleeuwen avatar Sep 26 '20 18:09 vincentleeuwen

Can anyone tell me why the solution of importing directly inside componentDidMount works?

LeOndaz avatar Jan 25 '21 16:01 LeOndaz

It's because componentDidMount and useEffect aren't run server-side. If the node.js server doesn't evaluate the contents of the react-facebook-pixel module, then it doesn't hit the code that uses window, which is what's causing the issue.

lineape avatar Jan 25 '21 16:01 lineape

@lineape Thanks for your fast response, I understand that both run on the Client, however, if both run on the client, does this mean the react facebook pixel just mentions window directly in it's source code which means that the error occurs at import time?

LeOndaz avatar Jan 25 '21 16:01 LeOndaz

Yes.

This line would throw with an uncaught ReferenceError

https://github.com/zsajjad/react-facebook-pixel/blob/fb4deb12558be23197bddfc89e3eefedd647f82a/src/index.js#L78

Edit: on second thought, it might not, it would only throw when the init method is called. It's been a few months since I looked into this and that was the issue at the time. For all I know it works now :P

lineape avatar Jan 25 '21 17:01 lineape

Yea yea, thanks, appreciated

LeOndaz avatar Jan 25 '21 17:01 LeOndaz

I'm pretty new to Nextjs (and to react for that matter). Do you have suggestions on how I could combine this with a cookie consent box? Is that even possible if it's SSG?

DiegoMcDipster avatar Jan 29 '21 19:01 DiegoMcDipster

I'm pretty new to Nextjs (and to react for that matter). Do you have suggestions on how I could combine this with a cookie consent box? Is that even possible if it's SSG?

Combine in what way? Like, only track page views if they've given cookie consent? Pretty easy.

The react-cookie-consent module for example has a getCookieConsentValue method, so a really naive implementation could be something like this:

Somewhere you'd have something like this for getting their consent

import CookieConsent from 'react-cookie-consent';

function Page() {
  return (
    <div>
      <h1>Hello World</h1>
      <CookieConsent location="bottom" />
    </div>
  );
}

And then you'd have your page tracking somewhere in your _app.tsx

import { getCookieConsentValue } from 'react-cookie-consent';

function useFacebookPageTracking() {
  useEffect(() => {
     if (getCookieConsentValue() !== 'true') return;
     
     // then do the tracking
  }, [getCookieConsentValue()]);
}

Note: I have tested none of this code, this is just an example of how to tackle it.

lineape avatar Jan 29 '21 19:01 lineape

@lineape thanks for the guidance! I got it working using react-cookie-consent and the code provided above by @ashconnell. Really appreciate you guys taking the time to share your knowledge!

DiegoMcDipster avatar Feb 01 '21 09:02 DiegoMcDipster

Hello all, Im trying to use this library in Nextjs and i followed @vincentleeuwen approach. And i also tried using the default scripts provided on pixel documention. I was able to config Pixel properly in localhost but when i deployed this version it says that detects the pixel but it shows a warning in PageView

localhost: Screenshot_86

Dev and Production: Screenshot_87

tsm20 avatar Mar 31 '21 10:03 tsm20

Use https://github.com/vercel/next.js/tree/canary/examples/with-facebook-pixel

NishargShah avatar Apr 22 '21 07:04 NishargShah

This way worked for me. Create a _document.js on your pages folder like this one and add the pixel to a script tag with dangerously innerHtml.

        <Head>
          {/* Facebook Pixel Code */}
          <script
            dangerouslySetInnerHTML={{
              __html: `!function(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='2.0';
                n.queue=[];t=b.createElement(e);t.async=!0;
                t.src=v;s=b.getElementsByTagName(e)[0];
                s.parentNode.insertBefore(t,s)}(window, document,'script',
                'https://connect.facebook.net/en_US/fbevents.js');
                fbq('init', 'YOURID');
                fbq('track', 'PageView');`,
            }}
          />
          {/* End Facebook Pixel Code */}
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>

diogosilva95 avatar May 11 '21 15:05 diogosilva95

Thank you @ashconnell , your code was very useful. The problem with it, that it only records PageView event. Also thank you @NishargShah for pointing to Next Js example. Which handles the early mentioned problem, but it has two problems:

  1. We cannot reinitialise pixel when user logs in
  2. Putting a script like that may block rendering and then increase FCP. So I came up with this solution using react context Create a context file with the following code, then you can call recordFBEvent whenever you want even in a function like "purchase" Also it will reinitialise Pixel when user logs in.
import { createContext, useState, useEffect } from "react"
  import { connect } from "react-redux"
  import EnvironmentConfig from "../env-config"
  
  const FACEBOOK_PIXEL_ID = process.env.NEXT_PUBLIC_FACEBOOK_PIXEL_ID
  const ReactPixelContext = createContext({
    recordeFBEvent: function (event, data) {},
  })
  const mapStateToProps = (state) => ({
    currentCustomer: state.customer.currentCustomer,
  })
  export const ReactPixelContextProvider = connect(mapStateToProps)((props) => {
    const [reactPixel, setReactPixel] = useState()
    let beingInitialized = false
    const init = (customer, event, data) => {
      if (!FACEBOOK_PIXEL_ID) return
      let fb
      if ((!reactPixel || customer) && !beingInitialized) {
        beingInitialized = true
        import("react-facebook-pixel")
          .then((module) => (fb = module.default))
          .then(() => {
            let adavancedMatching = {}
            if (customer) {
              if (customer.email) {
                adavancedMatching.em = customer.email
              }
              if (customer.mobileNumber) {
                adavancedMatching.ph = customer.mobileNumber
              }
            }
            const options = {
              autoConfig: true,
              debug: EnvironmentConfig.current !== "prod",
            }
            console.log("initialize with matching: ")
            console.log(adavancedMatching)
            fb.init(FACEBOOK_PIXEL_ID, adavancedMatching, options)
            if (event && data) {
              console.log(event)
              console.log(data)
              fb.track(event, data)
            }
            setReactPixel(fb)
            beingInitialized = false
          })
      }
    }
    useEffect(() => {
      if (!beingInitialized) {
        init()
      }
    }, [])
    useEffect(() => {
      console.log("should be re-initialized with custoemr")
      console.log(props.currentCustomer)
      if (!beingInitialized) {
        init(props.currentCustomer)
      }
    }, [props.currentCustomer])
    const recordEventHandler = (event, data) => {
      if (!reactPixel) {
        console.log(event)
        console.log(data)
        init(null, event, data)
        return
      }
      console.log(event)
      console.log(data)
      reactPixel.track(event, data)
    }
    const context = { recordeFBEvent: recordEventHandler }
    return (
      <ReactPixelContext.Provider value={context}>
        {props.children}
      </ReactPixelContext.Provider>
    )
  })
  
  export default ReactPixelContext

It might not be perfect, so I'm looking forward to your comments

syriail avatar May 27 '21 10:05 syriail

Hello all, Im trying to use this library in Nextjs and i followed @vincentleeuwen approach. And i also tried using the default scripts provided on pixel documention. I was able to config Pixel properly in localhost but when i deployed this version it says that detects the pixel but it shows a warning in PageView

localhost: Screenshot_86

Dev and Production: Screenshot_87

any update on this i am facing same issue? i am using this reference https://github.com/vercel/next.js/tree/canary/examples/with-facebook-pixel but some how i am facing same issue as mention @tsm20

heet-solutelabs avatar Jun 28 '21 16:06 heet-solutelabs

Following @vincentleeuwen the below code did work for me, though Facebook Pixel Helper still can't identify if my app has Pixel configured or not but the console logs proves it's working:

useEffect(async () => {
        const { default: ReactPixel } = await import('react-facebook-pixel');
        ReactPixel.init(FB_PIXEL, null, {
            autoConfig: true,
            debug: true,
          });
        ReactPixel.pageView();
        ReactPixel.track("ViewContent")
    });

Also, do pause your ad-blockers if any to see pixel work.

Guneetgstar avatar Jul 06 '21 22:07 Guneetgstar

The problem is that It only records PageView events for all the above workarounds, and I'd like to track 'purchase' events in an online store created by Next.js. @syriail Thank you for your solution, but it is very complicated for importing react-redux and context 😂. Is there an easier workaround?

cnscorpions avatar Jan 01 '22 15:01 cnscorpions

My workaround for purchase event is as follows:

// in utils/fb.js
export const trackFbPageView = async () => {
    const { default: ReactPixel } = await import('react-facebook-pixel');
    ReactPixel.init('*****') // facebookPixelId
    ReactPixel.pageView();
}
  
export const trackFbPurchase = async () => {
    const { default: ReactPixel } = await import('react-facebook-pixel');
    ReactPixel.init('*****') // facebookPixelId
    ReactPixel.track('Purchase', {currency: "USD", value: 29.9})
}

I am not sure whether reinitializing ReactPixel has a bad impact on my website performance.

cnscorpions avatar Jan 01 '22 16:01 cnscorpions