Session replay for Next.js

Load the recorder with next/script in your root layout. It needs no npm package and works with the App Router and the Pages Router.

You need a running Replayowl server first. See Deploy to Cloudflare Workers. Below, replace replay.example.com with your server’s domain.

App Router

In app/layout.tsx:

import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://replay.example.com/recorder.js"
          data-endpoint="https://replay.example.com/api/ingest"
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}

Pages Router

Put the same <Script> in pages/_app.tsx, next to <Component {...pageProps} />.

Attaching the signed-in user

Call identify once you know who is signed in. window.ReplayOwl exists only after the script has loaded, so wait for it if needed:

"use client";
import { useEffect } from "react";

declare global {
  interface Window { ReplayOwl?: { identify(id: string): void } }
}

export function ReplayIdentity({ userId }: { userId?: string }) {
  useEffect(() => {
    if (!userId) return;
    const tryIdentify = () => window.ReplayOwl?.identify(userId);
    if (window.ReplayOwl) tryIdentify();
    else window.addEventListener("load", tryIdentify, { once: true });
  }, [userId]);
  return null;
}

Client-side navigation

Route changes made by the Next.js router are recorded and replayed like any other change to the page. The URL is read on full page loads only, so the session list and page searches show the page where the visit started and any pages loaded in full. See limitations.

Privacy

Inputs, emails and card numbers are masked by default. Add data-ro-mask or data-ro-block to components that show sensitive data. See Masking and privacy.