DEV Community

Cover image for How to setup Mixpanel Analytics in Next.js
Bojan Stanojevic
Bojan Stanojevic

Posted on • Edited on

How to setup Mixpanel Analytics in Next.js

Introduction

Mixpanel is a powerful, user-friendly analytics platform that can help anyone looking to better understand user behavior and make data-driven decisions. Unlike traditional page-view focused analytics tools, Mixpanel specializes in event-based tracking with a dashboard that is so easy to use you don't have to spend any time to learn how to use it. In this post we'll go into why you should consider this tool and how to integrate it with your Next.js application.

Why to choose Mixpanel

There are many cool features that I love about Mixpanel and you can check some of them below, but main reason for me is a generous free tier. Basically you don't have to spend any money on this tool until you scaled so much that it makes sense. You get 20 million events per month and most reports are available on the free plan. Out of all the other analytics platforms I checked out this is by far the best. Amplitude focuses on monthly tracked users and is more expensive, Appsflyer does not focus on product analytics primarily and has a much wider scope of services, Posthog only gives you 1 million events on a free plan if you opt in for cloud hosting, and Plausible is based on page-view tracking and does not offer free tier. There is also Google Analytics 4 which I avoid like the plague and will not even consider for product analytics.

Key features of Mixpanel

Besides the pricing Mixpanel has some awesome features that will make tracking easy for you:

Event-based tracking: With event-based tracking you can track page views, but also anything else you want, you also have full control what page views you want to track which is great because it gives you more control.

Real-time data: Unlike GA4 where data can lag quite a bit, you get data right away.

Linking user data: If you are authenticating users, Mixpanel has the ability to link events before their authenticated with events when they authenticated so you get better data flow.

Reports: With Mixpanel on the free plan you get four types of reports: Funnels, Insights, Flows and Retention. These reports allow you to track drop offs, specific events and how well your app retains users over time.

Cross-platform tracking: Analyze user behavior across web, android and ios.

Server-Side Tracking or Client-Side?

Mixpanel has the ability to track events on the server-side and on the cient-side as well. You can also use a hybrid approach where you'll send events from client and server side as well.

What Mixpanel suggests is to send every event server-side when possible, the reason for this is data accuracy. Basically Adblockers pose an issue when sending data client-side, they block those events, since Mixpanel is a well known tracking tool. This leads to you losing data even up to 30%. One solution is to use a proxy server, but the best option is to send data server side which is what we'll focus on in this tutorial.

Setting Up Mixpanel in Next.js

To setup Mixpanel first register an account at Mixpanel and get your project token. Then install mixpanel-node library to be able to send events server-side.

npm i mixpanel
Enter fullscreen mode Exit fullscreen mode

Next up, set up an API route inside app/api/mixpanel/route.ts

import { NextResponse } from "next/server";
const Mixpanel = require("mixpanel");
const mixpanel = Mixpanel.init(process.env.MIXPANEL_TOKEN);

export async function POST(request: Request) {
  const data = await request.json();
  try {
    const { event, properties } = data;

    mixpanel.track(event, properties);

    return NextResponse.json({ status: "Event tracked successfully" });
  } catch (error) {
    console.log(error);
    return NextResponse.json(
      { error: "Internal Server Error" },
      { status: 500 }
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Inside this route we are initializing Mixpanel library, getting data from the application and calling mixpanel.track that consists of event name and other properties we will include in the call.

Next up, I created a function sendToMixpanel inside lib/sendToMixpanel.tsx that handles data that will be sent to the API. Since we are sending data server side we can't get additional information that Mixpanel collects on the client side automatically like location, device info, and query params. This function will get that information and it looks like this:

import { v4 as uuidv4 } from "uuid";

const sendToMixpanel = async (
  eventName: string,
  eventProperties?: Record<string, any>
) => {
//here we are getting the location from a separate endpoint /api/proxy
  const locationResponse = await fetch("/api/proxy");
  const locationData = await locationResponse.json();

//this part of code handles getting the UTM parameters that we can't get by default server side
  const urlParams = new URLSearchParams(window.location.search);
  const utmParams = {
    utm_source: urlParams.get("utm_source") || undefined,
    utm_medium: urlParams.get("utm_medium") || undefined,
    utm_campaign: urlParams.get("utm_campaign") || undefined,
    utm_term: urlParams.get("utm_term") || undefined,
    utm_content: urlParams.get("utm_content") || undefined,
    id: urlParams.get("id") || undefined,
  };

//In my application I'm not authenticating users, so here I'm using uuid library to assign a random id that I will assign to users based on their session, if you are authenticating users you will have to do some additional steps
//More info here: https://docs.mixpanel.com/docs/tracking-methods/id-management/identifying-users

function getUserUUID() {
    let userUUID = localStorage.getItem("userUUID");
    if (!userUUID) {
      userUUID = uuidv4();
      localStorage.setItem("userUUID", userUUID);
    }
    return userUUID;
  }
  const userUUID = getUserUUID();

//Here we are including additional data that will be sent to Mixpanel like device information, UTM parameters and location
  const additionalProperties = {
    distinct_id: userUUID,
    $user_id: userUUID,
    $browser: navigator.userAgent,
    $browser_version: navigator.appVersion,
    $city: locationData.city,
    $region: locationData.region_name,
    mp_country_code: locationData.country_name,
    $current_url: window.location.href,
    $device: navigator.platform,
    $device_id: navigator.userAgent,
    $initial_referrer: document.referrer ? document.referrer : undefined,
    $initial_referring_domain: document.referrer
      ? new URL(document.referrer).hostname
      : undefined,
    $os: navigator.platform,
    $screen_height: window.screen.height,
    $screen_width: window.screen.width,
    ...utmParams,
  };
  const properties = {
    ...eventProperties,
    ...additionalProperties,
  };
//Finally we are calling the mixpanel api route
  fetch("/api/mixpanel", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      event: eventName,
      properties: properties,
    }),
  });
};

export default sendToMixpanel;

Enter fullscreen mode Exit fullscreen mode

Finally we need a proxy route that will get the location data. Here I am using ipapi API to get the location data since I found it the most reliable but feel free to use whichever service you want. This endpoint is at api/proxy/route.ts

import { NextResponse, NextRequest } from "next/server";

export async function GET(request: NextRequest) {
  try {
    // We extract client IP address here
    const ip =
      request.headers.get("x-forwarded-for") ||
      request.headers.get("x-real-ip") ||
      request.ip;

    // If the IP address is not found, we return an error
    if (!ip) {
      return NextResponse.json(
        { error: "Unable to determine IP address" },
        { status: 400 }
      );
    }

 // calling api.ipapi API to get the location data based on IP address
    const locationResponse = await fetch(
      `http://api.ipapi.com/${ip}?access_key=${process.env.IPAPI_KEY}&format=1`
    );
    const locationData = await locationResponse.json();
    return NextResponse.json(locationData);
  } catch (error) {
    console.log(error);
    return NextResponse.json(
      { error: "Internal Server Error" },
      { status: 500 }
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

Sending Events to Mixpanel

Once this is set up, we can call sendToMixpanel() function wherever we want inside our Next.js app. Here are a few examples from my app:

Page view:

 useEffect(() => {
    sendToMixpanel("page_view");
  }, []);
Enter fullscreen mode Exit fullscreen mode

Modal opened:

 <FancyButton
   className="shadow-2xl px-4 py-2"
   onClick={() => sendToMixpanel("contact_opened")}
 >
   Contact us
</FancyButton>
Enter fullscreen mode Exit fullscreen mode

Link clicked:

<Link
   href={kodawarian.url}
   target="_blank"
   onClick={() =>
   sendToMixpanel("link_clicked", {
   url: kodawarian.url,
   name: kodawarian.name,
   })
   }
>
Enter fullscreen mode Exit fullscreen mode

Finally, when you open up Mixpanel events page you will be able to see your data flow inside the platform, and then you can start playing around with the reports.

Mixpanel Events Dashboard

Conclusion

And that's it, I think this is pretty straightforward but I would love to hear your thoughts and suggestions! Let me know if you would like a tutorial on how to use Mixpanel even though platform is very user-friendly. I stumbled across a couple of tutorials on setting up Mixpanel in Next.js, but none of them explained in more details everything I consider important so I thought I should make one.

If you need help in setting up product analytics for your project feel free to book a call here.
If you enjoyed this post I'd love it if you could give me a follow on Twitter by clicking on the button below! :)
Dellboyan Twitter

Top comments (2)

Collapse
 
farhadjaman profile image
Farhad

External properties like UTM properties come when I track the event directly from the client side. Is the data still persisting after I send it through the API route?

Collapse
 
dellboyan profile image
Bojan Stanojevic

No, you'll need to get those values and pass them on to your event when sending server side.