DEV Community

Cover image for Using Mixpanel via proxy with Next.js rewrites
Joel Rainwater
Joel Rainwater

Posted on • Originally published at blog.rainwater.io

Using Mixpanel via proxy with Next.js rewrites

Quick Overview

Mixpanel has great documentation for setting up your project in many different languages, including JavaScript. They also provide docs and examples for implenting tracking with a proxy. Setting up Mixpanel through a proxy is useful to bypass ad and tracking blockers, and is a nice way to keep all client requests through your domain.

I didn't see any great resources out there for setting up Mixpanel specifically in Next.js using their built-in Rewrites feature to accomplish the same goal as setting up a proxy. Using rewrites, I was able to keep all Mixpanel requests going through the same domain as my website/app, and didn't need to deal with configuring Nginx myself (I'm hosting on Vercel, so I don't normally have to touch webserver config).

This is how I implemented that setup.

Step 1: Install Mixpanel's JS SDK

We are going to use Mixpanel's JS SDK mixpanel-browser. You can install it with npm/yarn as follows.

# npm
npm install --save mixpanel-browser
# yarn
yarn add mixpanel-browser
Enter fullscreen mode Exit fullscreen mode

If you're using TypeScript, you can also install @types/mixpanel-browser to have typings.

Step 2: Create Mixpanel wrapper function

Note - This post is to explain the setup for using Next rewrites. I used this article as a guide for creating a basic Mixpanel wrapper function.

Create a file called mixpanel.{ts|js} wherever it makes sense in your project. We will be defining an object which will be imported and used anywhere you plan to implement Mixpanel tracking.

// Remove { Dict, Query } if not using TypeScript
import mixpanel, { Dict, Query } from "mixpanel-browser";

const isProd = process.env.NODE_ENV === "production";

mixpanel.init("YOUR_MIXPANEL_TOKEN", {
  // Use your project's URL, adding a slug for all Mixpanel requests
  api_host: "https://yourdomain.com/mp",
});

export const Mixpanel = {
  identify: (id: string) => {
    mixpanel.identify(id);
  },
  alias: (id: string) => {
    mixpanel.alias(id);
  },
  track: (name: string, props?: Dict) => {
    mixpanel.track(name, props);
  },
  track_links: (query: Query, name: string) => {
    mixpanel.track_links(query, name, {
      referrer: document.referrer,
    });
  },
  people: {
    set: (props: Dict) => {
      mixpanel.people.set(props);
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

First we import the mixpanel-browser library.

When in initialize it, we specify our own api_host. This is where we tell Mixpanel to use our URL instead of theirs. This url should be the same as your Next.js project's URL, with a specific slug which will be dedicated to only Mixpanel requests (e.g. /mp). You can make this whatever you want it to be, so long as it is not used anywhere else in your project.

Step 3: Add rewrites

Now we need to tell Next to handle the rewrites for the URL we just provided to Mixpanel.

In next.config.js add the following (yours might have additional configs already, the rewrites is what we care about).

/** @type {import('next').NextConfig} */
module.exports = {
  async rewrites() {
    return [
      {
        source: "/mp/lib.min.js",
        destination: "https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js",
      },
      {
        source: "/mp/lib.js",
        destination: "https://cdn.mxpnl.com/libs/mixpanel-2-latest.js",
      },
      {
        source: "/mp/decide",
        destination: "https://decide.mixpanel.com/decide",
      },
      {
        source: "/mp/:slug",
        // use "api-eu.mixpanel.com" if you need to use EU servers
        destination: "https://api.mixpanel.com/:slug",
      },
    ];
  },
};
Enter fullscreen mode Exit fullscreen mode

This tells Next, when a request is made to each of these endpoints, it will perform a rewrite to the destination URL. You can find more details about these rewrites in Next's documentation.

Step 4: Track things

Now you can use your Mixpanel object throughout your Next project. Import it using import { Mixpanel } from './mixpanel';

Then use it to track events, link clicks, and anything else useful. (These are just examples, not indicative of useful events to track).


import { useEffect } from "react";
import Link from "next/link";
import { Mixpanel } from "./path/to/mixpanel";

const PageName = () => {
  useEffect(() => {
    Mixpanel.track("Loaded PageName");
    Mixpanel.track_links("#nav a", "Nav link clicked");
  }, []);

  const handleButtonClick = () => {
    Mixpanel.track("Button clicked");
    alert("Button clicked!");
  };

  return (
    <div className="container">
      <div id="nav">
        <Link href="/dashboard">
          <a>Home</a>
        </Link>
        <Link href="/about">
          <a>About</a>
        </Link>
        <Link href="/pricing">
          <a>Pricing</a>
        </Link>
      </div>

      <button onClick={handleButtonClick}>Click me!</button>
    </div>
  );
};

export default PageName;
Enter fullscreen mode Exit fullscreen mode

Wrapping up

There you have it. All requests for Mixpanel should now be going through your app's domain, effectively working as a proxy using Next's built-in Rewrites capabilities.

Let me know if this worked for you, or if you have suggestions for a better approach in the comments!

Top comments (3)

Collapse
 
irfancoder profile image
Irfan Ismail

I tried to implement this, but found that, tracking no longer provides the geolocation of the event. I assume this is because of the proxy, where the IP of the request is being overidden server-side. Any idea how to solve this?

Collapse
 
petrbrzek profile image
Petr Brzek • Edited

I have dealt with the same and it can be solved via middleware.

import { NextResponse } from 'next/server'
import requestIp from 'request-ip'

export const config = {
  matcher: '/mp/:path*',
}

export function middleware(request) {
  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('X-REAL-IP', requestIp.getClientIp(request) || request.ip)

  return NextResponse.rewrite(request.nextUrl, {
    request: {
      headers: requestHeaders,
    },
  })
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
rain2o profile image
Joel Rainwater

Ah, nice one. Thanks @petrbrzek that looks like a good solution. I’ll have to try that out next time.

Sorry, I didn’t get notifications of these comments.