Skip to content

Instantly share code, notes, and snippets.

@AWolf81
Last active March 6, 2024 13:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AWolf81/98d622415bf9a87f3dfb8369b7c44f8c to your computer and use it in GitHub Desktop.
Save AWolf81/98d622415bf9a87f3dfb8369b7c44f8c to your computer and use it in GitHub Desktop.
Mixpanel & Cookiebot with Next.js

Usage

Installation

Install analytics and @analytics/mixpanel with

npm install analytics @analytics/mixpanel

Add tokens to your .env file

# Mixpanel project token (https://eu.mixpanel.com/project)
NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN=<your-mixpanel-token>

# Cookiebot id (https://admin.cookiebot.com/)
NEXT_PUBLIC_COOKIEBOT_ID=<your-cookiebot-token>

Also create a .env.example file with the above tokens without the token and ensure that .env is not tracked - double check at next commit. This is useful for a fresh cloned repository.

Add tokens to your production deploy

If your using Netlify head to your config https://app.netlify.com/sites/<your-site>/configuration/env and add the tokens, so they're available at the next deploy.

Add the hook to your layout

In your layout.tsx or providers.tsx (if you're using a provider component). Add the Hook and render the Consent script.

// ...other imports ...
import { useAnalytics } from '../utils/analytics' // see code from the Gist

export default function RootLayout({children}:{children: React.ReactNode}) {
  const { ConsentComponent } = useAnalytics();
  return (
    <html>
      <body>
        <div>
          { /* ... other components ... */ }
          {children}        
        </div>
        <ConsentComponent />
      </body>
    </html>
  )
}

UseAnalytics is also returning analytics object that could be used to pass to AppContext and used in your components. Omitted in the example.

Docs for analytics api can be found here.

Why is the onload needed in anayltics.tsx?

Without it the global window.Cookiebot was undefined as the script run before the cookiebot script is loaded. With the onload it is delaying the consent check until the Cookiebot global is available and the check is working as expected.

Configure Cookiebot Domain

Go to https://admin.cookiebot.com/domain-groups/ and add your production domain. e.g. ydecide.app

I haven't tested to add localhost and also wildcards are unfortunately not supported.

Test your deployed page

  • On first load you should see the consent modal
  • Consent everything and open network tab of your browser - look for xhr requests with track for Mixpanel tracking
  • Disable consent & reload page

Things to improve

Very first page is not tracked because the consent mechanism is only working on page load. Could be improved by adding an event listener CookiebotOnAccept with window.addEventListener('CookiebotOnAccept', function (e) {}). This would also require to add useState for the consent state. Maybe I'll add this later.

// License MIT
import React from 'react'
import Analytics from 'analytics'
import { Router } from 'next/router'
import mixpanelPlugin from '@analytics/mixpanel'
import Script from 'next/script'
const isProd = process.env.NODE_ENV === 'production'
const analytics = Analytics({
app: 'ydecide-web',
plugins: [
mixpanelPlugin({
token: process.env.NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN
})
]
})
export const useAnalytics = () => {
const [cookiebotLoaded, setLoaded] = React.useState(false)
const handleScriptLoad = () => {
setLoaded(true);
}
React.useEffect(() => {
// Wait for cookiebotScript loaded
if (!cookiebotLoaded) {
return
}
// We have marketing consent, consent in general and user has not set browser's doNotTrack setting
const consent = window?.Cookiebot?.consented &&
window?.Cookiebot?.consent?.marketing &&
!window?.Cookiebot?.doNotTrack
// Fire initial page view
if (isProd && consent) {
analytics.page()
}
// Fire page views on routing
const handleRouteChange = (/* url:string */) => {
if (isProd && consent) {
// We need to wrap it in a rAF to ensure the correct data is sent to Segment
// https://github.com/zeit/next.js/issues/6025
requestAnimationFrame(() => {
analytics.page()
})
}
}
Router.events.on('routeChangeComplete', handleRouteChange)
return () => Router.events.off('routeChangeComplete', handleRouteChange)
}, [ cookiebotLoaded ])
return {
analytics,
ConsentComponent: () => process.env.NEXT_PUBLIC_COOKIEBOT_ID && <Script id="Cookiebot" src="https://consent.cookiebot.com/uc.js" data-cbid={process.env.NEXT_PUBLIC_COOKIEBOT_ID} data-blockingmode="auto" type="text/javascript" onLoad={handleScriptLoad}/>
}
}
@visualcookie
Copy link

Hi. I'm wondering how this exactly worked for you, since there does not seem to be any types for the @analytics/mixpanel package.

@AWolf81
Copy link
Author

AWolf81 commented Mar 6, 2024

Hi @visualcookie:

Yes, you're right. Unfortunately, there are no types for the package. A workaround for it is to add src/@types/@analytics/mixpanel.d.ts with the following content:

declare module '@analytics/mixpanel';

Sorry, I've missed to add that detail.
I used this but a better type definition would be great.
But I haven't invested that much time into it.

If you're having a type definition it would be great to see or maybe add to that package.

Bye
Alex

@visualcookie
Copy link

Hi @visualcookie:

Yes, you're right. Unfortunately, there are no types for the package. A workaround for it is to add src/@types/@analytics/mixpanel.d.ts with the following content:

declare module '@analytics/mixpanel';

Sorry, I've missed to add that detail. I used this but a better type definition would be great. But I haven't invested that much time into it.

If you're having a type definition it would be great to see or maybe add to that package.

Bye Alex

Hej @AWolf81,

I've also just read a lot of people actually having to do exactly that with the other plugins. What I've done for now is this:

declare module '@analytics/mixpanel' {
  import type { AnalyticsPlugin } from 'analytics'

  export interface MixpanelPluginConfig {
    token: string
  }

  function mixpanelPlugin(config: MixpanelPluginConfig): AnalyticsPlugin

  export default mixpanelPlugin
}

I only typed token here since I think that's all the plugin needs. Haven't checked the plugin source code in general tho.

Thanks for the headsup and fast response 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment