Using OpenReplay with Next.js

Getting the tracker to work on a Next.js application is quite straightforward. Since Next.js works on top of React, the process is quite similar, the main difficulty comes from the Server Side Rendering capabilities of Next.js.

But once you understand how they work and how to structure your client-only code, you’ll be tracking your users in no time.

While you can get a copy&paste version of the code directly from the platform when you create a new project, that code is too barebone and might not follow the best practices expected by your team or company.

This method provides minimalistic approach where we inject the tracker into the app as a component, which will load the library dynamically.

App Router (v14+) - use client

Section titled App Router (v14+) - use client
'use client'
import { useEffect } from "react"
// note that you can manually import Tracker class if you want to handle the instance manually
import { tracker } from '@openreplay/tracker'

tracker.configure({
  projectKey: PROJECT_KEY,
  ingestPoint: "https://openreplay.mydomain.com/ingest", // when dealing with the self-hosted version of OpenReplay
})

const Openreplay = () => {
  useEffect(() => {
    if (typeof window !== 'undefined') {
      tracker.start()
    }
  }, [])

  return null
}

export default Openreplay 
App Router - dynamically import Openreplay component instance into root layout
Section titled App Router - dynamically import Openreplay component instance into root layout
import dynamic from "next/dynamic";

const OpenReplayNoSSR = dynamic(() => import('//import the Openreplay instance'), {
  ssr: false, //disables Server-side pre-rendering so window won't be undefined
})

export default function RootLayout({ children }) {
  return (
    <html lang="en" >
      <body>
        {children}
        <OpenReplayNoSSR />
      </body>
    </html >
  );
}

Regular component (Next up to v13)

Section titled Regular component (Next up to v13)
[...]

function TrackerComponent() {
const [tracker, setTracker] = React.useState(null);

  React.useEffect(() => {
    (async function () {
      const { tracker } = await import("@openreplay/tracker");
      tracker.configure({
        projectKey: PROJECT_KEY,
        ingestPoint: "https://openreplay.mydomain.com/ingest", // when dealing with the self-hosted version of OpenReplay
        __DISABLE_SECURE_MODE: true,
      });

      setTracker(tracker);
    })();
  }, []);
[...]

this method lets you create a context, which in turn makes it easier to control the tracker state and call its various API.

import { createContext } from "react"
import {v4 as uuidV4} from 'uuid'
import { useReducer } from "react"

export const TrackerContext = createContext()

function defaultGetUserId() {
   return uuidV4() 
}

async function newTracker(config) {
    // we use dynamic import to make sure that tracker is started inside browser env
    const { tracker } = await import("@openreplay/tracker");
    const getUserId = (config?.userIdEnabled && config?.getUserId) ? config.getUserId : defaultGetUserId
    let userId = null;

    const trackerConfig = {
        projectKey: config?.projectKey || process.env.NEXT_PUBLIC_OPENREPLAY_PROJECT_KEY
    }

    tracker.configure(trackerConfig);
    
    if(config?.userIdEnabled) {
        userId = getUserId()
        tracker.setUserID(userId)
    }
    return tracker

}

function reducer(state, action) {
        switch(action.type) {
            case 'init': {
                if(!state.tracker) {
                    console.log("Instantiaing the tracker for the first time...")
                    return {...state, tracker: newTracker(state.config)}
                }
                return state
            }
            case 'start': {
                console.log("Starting tracker...")
                state.tracker.start()
                return state
            }
        }
    }

export default function TrackerProvider({children, config = {}}) {
    let [state, dispatch] = useReducer(reducer, {tracker: null, config})
    let value = {
        startTracking: () => dispatch({type: 'start'}),
        initTracker: () => dispatch({type: 'init'})
    }

    return <TrackerContext.Provider value={value}>{children}</TrackerContext.Provider>
}

This provider makes 2 methods available to the user:

  • initTracker: which instantiates the tracker with the provided configuration.
  • startTracking: which triggers the start of the tracking process.

This provider also takes on a “configuration” object that you can extend for future features. This example shows you how to enable, optionally, the ability to uniquely identify users. You can use a default UUID which would be unique for every user every time they visit the page. You can alternatively provide a custom getUserId function as part of the configuration.

With your custom function, you could be providing the customer ID of your store, or the user’s email addressa or anything that uniquely identifies the user within your app. This way you can track their session replays across time in case they have a particular problem that others aren’t reporting.

Handling the “projectKey” in your code

Section titled Handling the “projectKey” in your code

As a note, the projectKey that you configure is the one provided by OpenReplay’s platform. This value should not be stored hardcoded in your code for security reasons, but rather on some kind of configuration file/sytem.

For this example, I’m taking advantage of Next’s .env files. What you have to understand is that if you do not prefix the environmental variable with NEXT_PUBLIC your client-code (the tracker) will not have access to it.

If this is not acceptable, you can use getStaticProps from within the first page that your users visit to get that value prior to instantiating the tracker. You can see both examples below.

Import and wrap your application with the TrackerProvider we created before, like this:

import '../styles/globals.css'
import TrackerProvider from '../context/trackerContext'

function MyApp({ Component, pageProps }) {

  return <TrackerProvider>
        <Component {...pageProps} />
    </TrackerProvider>
}

export default MyApp

This is the _app.js file, which acts as the entrypoint for the entire application.

And then, you can trigger the tracking from whatever component you wish, like so:

import { useContext, useEffect } from 'react'
import { TrackerContext } from '../context/trackerContext'
import styles from '../styles/Home.module.css'

export default function YourComponent({projectKey}) {
    
    const {initTracker, startTracking} = useContext(TrackerContext)
    
    useEffect( () => {
       initTracker()
       startTracking()
    }, [])
//.... rest of your code
}

If you don’t need as much control, you can join both, the “init message” and the “start tracking message”, so that you only need to call a single function to get the tracking going.

For the full code of a working Next-based application, you can check out this repository.

If you have any issues setting up the tracker on your Next-based project, please reach out to us on our Slack community and ask our devs directly!