Integrating Amplitude Analytics with NextJS App: A Comprehensive Guide

August 26, 2023

3 min read

Integrating Amplitude Analytics with NextJS App: A Comprehensive Guide
Watch on YouTube

Let's add Amplitude analytics to a NextJS app!

In the example below, we will track page views by calling the trackEvent method on the analytics instance from the useEffect hook:

function MyApp({ Component, pageProps }: AppProps) {
  const { pathname } = router
  useEffect(() => {
    analytics.trackEvent("Visit page", { pathname })
  }, [pathname])

  return <p>my app</p>
}

export default MyApp

This is how you create the analytics instance for your app:

import { isProduction } from "shared"
import { shouldBeDefined } from "@increaser/utils/shouldBeDefined"
import { AmplitudeAnalytics } from "@increaser/ui/analytics/AmplitudeAnalytics"
import { LocalAnalytics } from "@increaser/ui/analytics/LocalAnalytics"

export const analytics = isProduction
  ? new AmplitudeAnalytics(
      shouldBeDefined(process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY)
    )
  : new LocalAnalytics()

First, we check if the environment is set to production. We want to avoid sending analytics events in development or testing environments.

export const isProduction = process.env.NODE_ENV === "production"

The shouldBeDefined function will throw an error if the value is undefined. This is useful to ensure we never forget to set the environment variable.

export function shouldBeDefined<T>(
  value: T | undefined,
  valueName: string = "value"
): T {
  if (value === undefined) {
    throw new Error(`${valueName} is undefined`)
  }

  return value
}

Both AmplitudeAnalytics and LocalAnalytics adhere to the same interface, which should include:

  • setUser: used to associate the user with the analytics events. This would only be used if your app or website has authenticated users.
  • trackEvent: used to track events. Extra info can be passed into the second argument, such as the name of the page in our previous example.
export interface Analytics {
  setUser: (id: string) => void
  trackEvent: (name: string, data?: Record<string, any>) => void
}

Here's how you would implement AmplitudeAnalytics:

import { Analytics } from "./Analytics"
import * as amplitude from "@amplitude/analytics-browser"

export class AmplitudeAnalytics implements Analytics {
  apiKey: string
  isInitialized: boolean = false

  constructor(apiKey: string) {
    this.apiKey = apiKey
  }

  private initialize() {
    if (!this.isInitialized) {
      amplitude.init(this.apiKey)
      this.isInitialized = true
    }
  }

  setUser(id: string) {
    this.initialize()
    amplitude.setUserId(id)
  }

  trackEvent(name: string, data?: Record<string, any>) {
    this.initialize()
    amplitude.track(name, data)
  }
}

It requires the @amplitude/analytics-browser package to be installed and an apiKey to be passed as a constructor argument. We don't initialize Amplitude immediately; instead, we only do so the first time setUser or trackEvent is called. This ensures that requests are only sent to Amplitude upon tracking.

Here's how you can implement LocalAnalytics:

import { Analytics } from "./Analytics"

export class LocalAnalytics implements Analytics {
  constructor() {
    console.log("Initialize local analytics")
  }

  setUser(id: string) {
    console.log("Set user for analytics: ", id)
  }

  trackEvent(name: string, data?: Record<string, any>) {
    console.log("Track event: ", name, data)
  }
}

This merely logs the events to the console. It's useful for debugging events during development.