zsajjad / react-facebook-pixel

React JS wrapper for Facebook's Pixel
MIT License
241 stars 85 forks source link

How to use this library with next.js #53

Open icecold21 opened 4 years ago

icecold21 commented 4 years ago

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

charinduedirisuriya commented 4 years ago

I am wondering the same.

lineape commented 4 years ago

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();
        });
      });
  }
}
fikip commented 4 years ago

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.

ScottSmith95 commented 4 years ago

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>
      //…
    </>
  );
}
omar-dulaimi commented 4 years ago

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

ashconnell commented 4 years ago

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.

vincentleeuwen commented 4 years ago

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();
};
LeOndaz commented 3 years ago

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

lineape commented 3 years ago

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.

LeOndaz commented 3 years ago

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

lineape commented 3 years ago

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

LeOndaz commented 3 years ago

Yea yea, thanks, appreciated

DiegoMcDipster commented 3 years ago

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?

lineape commented 3 years ago

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.

DiegoMcDipster commented 3 years ago

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

tsm20 commented 3 years ago

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

NishargShah commented 3 years ago

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

diogosilva95 commented 3 years ago

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>
syriail commented 3 years ago

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

heet-solutelabs commented 3 years ago

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

Guneetgstar commented 3 years ago

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.

cnscorpions commented 2 years ago

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 commented 2 years ago

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.

thallysondias commented 5 months ago

Hello guys, my solution working:

Create a component:

components/pixel-events.tsx

"use client";
import React, { useEffect } from "react";
import { usePathname, useSearchParams } from "next/navigation";

//FACEBOOK INIT
export const FacebookPixelEvents: React.FC = () => {
  const pathname = usePathname();
  const searchParams = useSearchParams();

  useEffect(() => {
    import("react-facebook-pixel")
      .then((x) => x.default)
      .then((ReactPixel) => {
        ReactPixel.init("xxxxxx"); //don't forget to change this
        ReactPixel.pageView();
      });
  }, [pathname, searchParams]);

  return null;
};

//CUSTOM EVENTS
export const CompleteRegistration: React.FC = () => {
  const pathname = usePathname();
  const searchParams = useSearchParams();

  useEffect(() => {
    import("react-facebook-pixel")
      .then((x) => x.default)
      .then((ReactPixel) => {
        ReactPixel.track('CompleteRegistration')
      });
  }, [pathname, searchParams]);

  return null;
};

On layout.tsx add you component after {children}, to fire "pageview" event on all pages.

....
 {children}
<Suspense fallback={null}>
          <FacebookPixelEvents />
 </Suspense>
 ....

For custom Events, just call the function. Example, i call "CompleteRegistration" when user come the "thank-you" page:

    <Suspense fallback={null}>
        <CompleteRegistration />
      </Suspense>

check all events here: https://developers.facebook.com/docs/meta-pixel/reference#standard-events