How to Integrate Mixpanel in a Scalable Way with React.js/Next.js
Learn the best practices for integrating Mixpanel with your React.js/Next.js application in a scalable manner.

Faisal Husain

I recently had the opportunity to integrate Mixpanel into my organization's Next.js application. However, I found that the existing blog posts on the topic were quite basic and lacked a type-safe approach. After some research and experimentation, I was able to develop a scalable and type-safe solution for integrating Mixpanel with our application. In this post, I'll share the best practices and techniques I discovered during this process. Although I am taking example of a next.js application it will work same with React.js app also.
Let's Start 👨💻👨💻
Let' break down into steps
- Create an account on Mixpanel and get your project token
- Create a Mixpanel context provider in your app and wrap around layout.
- Declare events in different file and import them to keep events type safe around the app.
Step 1
Go to Mixpanel.com and create you account and then create project and get your project token and safe then in .env as NEXT_PUBLIC_MIXPANEL_TOKEN (name obviously depends upon you 😅)
Here is how your token will look.

Step 2
First install npm package
npm i mixpanel-browser
npm i --save-dev @types/mixpanel-browser
Now create a file named mixpanel.tsx
"use client";
import { useEffect, createContext, useContext, ReactNode } from "react";
import mixpanel from "mixpanel-browser";
const MIXPANEL_TOKEN = process.env.NEXT_PUBLIC_MIXPANEL_TOKEN;
type MixpanelContextType = {
track: (
eventName: string,
properties?: { [key: string]: string | number | boolean }
) => void;
};
type MixpanelProviderProps = {
children: ReactNode;
};
const MixpanelContext = createContext<MixpanelContextType>({
track: () => { },
});
export const useMixpanel = () => useContext(MixpanelContext);
export function MixpanelProvider({ children }: MixpanelProviderProps) {
useEffect(() => {
if (typeof window !== "undefined") {
if (!MIXPANEL_TOKEN) {
console.warn("Mixpanel token is missing! Check your .env file.");
return;
}
mixpanel.init(MIXPANEL_TOKEN, {
debug: process.env.NODE_ENV === "development",
track_pageview: true,
persistence: "localStorage",
ignore_dnt: true,
});
}
}, []);
const track = (
eventName: string,
properties?: { [key: string]: string | number | boolean }
) => {
mixpanel.track(eventName, {
...properties,
timestamp: Date.now(),
path:
typeof window !== "undefined" ? window.location.pathname : undefined,
});
};
const value = { track };
return (
<MixpanelContext.Provider value={value}>
{children}
</MixpanelContext.Provider>
);
}
Yup thats your main file . Now you have created main provider for mixpanel now just wrap this around your root layout like
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { MixpanelProvider } from "@/lib/mixpanel";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
<MixpanelProvider>
{children}
</MixpanelProvider>
</body>
</html>
);
}
Now it should be working mixpanel track page view by default.
Step 3
Now I want to control what event I can send just to keep track so that nobody can send a random event which I dont want or thought of So for that create a file named mixpanelEvents.ts
export const MIXPANEL_ANALYTICS_EVENTS = {
PAGE_VIEW: 'Page View',
BUTTON_CLICK: 'Button Click',
SEARCH:"Search"
} as const;
And now update the mixpanel.ts a bit below is the updated code.
"use client";
import { useEffect, createContext, useContext, ReactNode } from "react";
import mixpanel from "mixpanel-browser";
import { MIXPANEL_ANALYTICS_EVENTS } from "./mixpanelEvents";
const MIXPANEL_TOKEN = process.env.NEXT_PUBLIC_MIXPANEL_TOKEN;
type MixpanelEventName =
(typeof MIXPANEL_ANALYTICS_EVENTS)[keyof typeof MIXPANEL_ANALYTICS_EVENTS];
type MixpanelContextType = {
track: (
eventName: MixpanelEventName,
properties?: { [key: string]: string | number | boolean }
) => void;
};
type MixpanelProviderProps = {
children: ReactNode;
};
const MixpanelContext = createContext<MixpanelContextType>({
track: () => { },
});
export const useMixpanel = () => useContext(MixpanelContext);
export function MixpanelProvider({ children }: MixpanelProviderProps) {
useEffect(() => {
if (typeof window !== "undefined") {
if (!MIXPANEL_TOKEN) {
console.warn("Mixpanel token is missing! Check your .env file.");
return;
}
mixpanel.init(MIXPANEL_TOKEN, {
debug: process.env.NODE_ENV === "development",
track_pageview: true,
persistence: "localStorage",
ignore_dnt: true,
});
}
}, []);
const track = (
eventName: MixpanelEventName,
properties?: { [key: string]: string | number | boolean }
) => {
mixpanel.track(eventName, {
...properties,
timestamp: Date.now(),
path:
typeof window !== "undefined" ? window.location.pathname : undefined,
});
};
const value = { track };
return (
<MixpanelContext.Provider value={value}>
{children}
</MixpanelContext.Provider>
);
}
Now its done mixpanel is integrated with proper typesafe approach.
Usage
"use client"
import { useMixpanel } from '@/lib/mixpanel';
export default function Home() {
const { track } = useMixpanel();
const handleClick = () => {
track("Button Click", {
buttonName: "Something",
location: "Somthing",
});
};
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start w-fit">
<div className="w-full">
<div className="border border-black/[.08] dark:border-white/[.145] rounded-lg p-10 bg-gradient-to-r from-blue-50 to-purple-50 dark:from-blue-950/40 dark:to-purple-950/40 flex flex-col items-center justify-center">
<h2 className="text-4xl font-semibold mb-3">This is Mixpanel Example Repo</h2>
<div className="flex gap-4 items-center flex-col sm:flex-row pt-10">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-blue-600 hover:bg-blue-700 text-white gap-2 font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto"
href="#"
target="_blank"
rel="noopener noreferrer"
onClick={handleClick}
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M13 5L20 12L13 19M4 12H20" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
Go to Blog
</a>
Bonus
Now you also want to keep the tracked of loggedin user just create one more events in mixpanel.tsx Below is the code.
"use client";
import { useEffect, createContext, useContext, ReactNode } from "react";
import mixpanel from "mixpanel-browser";
import { MIXPANEL_ANALYTICS_EVENTS } from "../config/mixpanelConfig";
import { useAuth } from "./auth-provider";
const MIXPANEL_TOKEN = process.env.NEXT_PUBLIC_MIXPANEL_TOKEN;
type MixpanelEventName =
(typeof MIXPANEL_ANALYTICS_EVENTS)[keyof typeof MIXPANEL_ANALYTICS_EVENTS];
type MixpanelContextType = {
track: (
eventName: MixpanelEventName,
properties?: { [key: string]: string | number | boolean }
) => void;
identifyKnownUsers: (
userId: string,
email:string,
userProperties?: Record<string, string | null>
) => void;
};
type MixpanelProviderProps = {
children: ReactNode;
};
const MixpanelContext = createContext<MixpanelContextType>({
track: () => {},
identifyKnownUsers: () => {},
});
export const useMixpanel = () => useContext(MixpanelContext);
export function MixpanelProvider({ children }: MixpanelProviderProps) {
const { currentUser, userLoggedIn } = useAuth();
useEffect(() => {
if (typeof window !== "undefined") {
if (!MIXPANEL_TOKEN) {
console.warn("Mixpanel token is missing! Check your .env file.");
return;
}
mixpanel.init(MIXPANEL_TOKEN, {
debug: process.env.NODE_ENV === "development",
track_pageview: true,
persistence: "localStorage",
ignore_dnt: true,
});
if (userLoggedIn && currentUser?.uid) {
identifyKnownUsers(currentUser.uid,currentUser.email as string);
}
}
}, [userLoggedIn, currentUser]);
const track = (
eventName: MixpanelEventName,
properties?: { [key: string]: string | number | boolean }
) => {
mixpanel.track(eventName, {
...properties,
timestamp: Date.now(),
path:
typeof window !== "undefined" ? window.location.pathname : undefined,
});
};
const identifyKnownUsers = (
userId: string,
email:string,
userProperties?: Record<string, string | null>
) => {
try {
mixpanel.identify(userId);
mixpanel.people.set({"$email":email});
if (userProperties) {
mixpanel.people.set(userProperties);
}
} catch (error) {
console.error("Error identifying user:", error);
}
};
const value = { track, identifyKnownUsers };
return (
<MixpanelContext.Provider value={value}>
{children}
</MixpanelContext.Provider>
);
}
Now by default this will also identify your user.If loggedIn .
If you encounter any issues or have further questions, feel free to reach out to me at hi@faisalhusa.in .
Happy coding!
Thanks for reading!