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.
Adding the tracking code
Section titled Adding the tracking codeWhile 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.
Tracker component
Section titled Tracker componentThis 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"
import Tracker from '@openreplay/tracker'
const tracker = new Tracker({
projectKey: "",
ingestPoint: "",
})
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 layoutimport 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");
const openRelayTracker = new Tracker.default({
projectKey: PROJECT_KEY,
__DISABLE_SECURE_MODE: true,
});
setTracker(openRelayTracker);
})();
}, []);
[...]
Tracker Context
Section titled Tracker Contextthis 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
}
const tracker = new Tracker(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.
💡A note for self-hosted users: if you’re using the self-hosted version of OpenReplay, you’ll also have to specify the ingestPoint
configuration property. This property specifies the ingestion endpoint for the tracker’s data. Cloud users don’t need this, because by default the tracker will now where the SaaS version of this endpoint is, but if you’re self-hosting it, you’ll need to specify it (it should be something like https://openreplay.mydomain.com/ingest
)
Handling the “projectKey” in your code
Section titled Handling the “projectKey” in your codeAs 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.
Using the tracker context
Section titled Using the tracker contextImport 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.
Have questions?
Section titled Have questions?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!