Next.js real user monitoring
The Osuite RUM SDK (@osuite/rum) is a browser SDK — it reads window, localStorage, and BroadcastChannel, so it must initialize on the client and never during server rendering. In the Next.js App Router that means a small 'use client' component that starts the SDK in an effect and is mounted from your root layout. Once running, it captures page-load and route-change spans, frontend errors, session replay, and end-to-end trace correlation with your backend.
Prerequisites
Section titled “Prerequisites”- A Next.js 13.4 or newer app (App Router or Pages Router).
- An Osuite ingest token from Settings → API Keys.
- Your ingest region (
ingest.<region>.osuite.io).
Install
Section titled “Install”npm install @osuite/rum# orpnpm add @osuite/rumReact is already present in a Next.js app, so the error boundary from @osuite/rum/react needs no extra install.
Initialize on the client
Section titled “Initialize on the client”-
Create a client component that starts the SDK
Mark it
'use client'and callosuite.initinsideuseEffectso it runs only in the browser. A module-scope guard keeps it to a single init across React Strict Mode’s double-invoke in development. The component renders nothing.app/osuite-rum.tsx 'use client';import { useEffect } from 'react';import { osuite } from '@osuite/rum';let started = false;export function OsuiteRum() {useEffect(() => {if (started) return;started = true;osuite.init({apiKey: process.env.NEXT_PUBLIC_OSUITE_INGEST_TOKEN,endpoint: 'https://ingest.<region>.osuite.io',apiEndpoint: 'https://api.<region>.osuite.io',service: {name: 'frontend-web',version: process.env.NEXT_PUBLIC_BUILD_SHA ?? 'dev',environment: process.env.NEXT_PUBLIC_ENV ?? 'development',},});}, []);return null;} -
Mount it from the root layout
The layout is a server component; rendering the client component from it is fine. Place
<OsuiteRum />inside<body>.app/layout.tsx import { OsuiteRum } from './osuite-rum';export default function RootLayout({ children }: { children: React.ReactNode }) {return (<html lang="en"><body><OsuiteRum />{children}</body></html>);} -
Set the environment variables
The SDK runs in the browser, so every value it needs must be exposed with the
NEXT_PUBLIC_prefix — variables without it are stripped from the client bundle. The ingest token is public by design (rate-limited per key), so shipping it to the browser is expected..env.local NEXT_PUBLIC_OSUITE_INGEST_TOKEN=<your-ingest-token>NEXT_PUBLIC_ENV=productionNEXT_PUBLIC_BUILD_SHA=dev
endpoint is the OTLP host for traces and logs; apiEndpoint is the control-plane host for session-replay uploads — they are different hosts, both required. Every option beyond apiKey / endpoint / apiEndpoint / service is documented in the SDK reference.
Avoid SSR pitfalls
Section titled “Avoid SSR pitfalls”Route changes
Section titled “Route changes”Route changes are captured automatically. The SDK’s navigation tracker defaults to framework: 'auto', which detects Next.js (via __NEXT_DATA__ / __next_f) and uses the Next adapter; both App Router and Pages Router navigations emit a route_change span with no extra wiring. Each span adopts the fetch and XHR calls fired during the transition and settles when the DOM goes quiet. See the navigation configuration to tune or disable it.
Catch render errors
Section titled “Catch render errors”OsuiteErrorBoundary from @osuite/rum/react is a React error boundary, so it must live in a client component. Wrap it around your app content to report render crashes as browser.react_error logs and trigger a sampling upgrade:
'use client';
import { OsuiteErrorBoundary } from '@osuite/rum/react';
export function RumBoundary({ children }: { children: React.ReactNode }) { return ( <OsuiteErrorBoundary fallback={<p>Something went wrong.</p>}> {children} </OsuiteErrorBoundary> );}Then wrap {children} with it in the root layout, alongside <OsuiteRum />.
Runtime errors, unhandled Promise rejections, and failed resource loads are captured by default once the SDK is running. See Frontend errors for every source and manual capture.
Source maps
Section titled “Source maps”Set service.version to your build identifier (a git SHA or release tag) so stack traces in frontend errors resolve against the matching source maps. Keep the same value — NEXT_PUBLIC_BUILD_SHA above — consistent between your build and this config.
Verify in Osuite
Section titled “Verify in Osuite”What you should see
Load a page and navigate between routes, then open RUM. Your session appears within a minute under the frontend-web service, with page-load and route_change spans and a session replay attached. Trigger a handled error with osuite.captureException and confirm it lands in the frontend error list, linked to the session that produced it.
Troubleshooting
Section titled “Troubleshooting”No data in Osuite after a few minutes? Work through these checks.
- Init ran in the browser — confirm the
OsuiteRumcomponent is mounted in the layout and the effect fired. Setdebug: true(see thedebugoption) to print init and transport diagnostics toconsole.debug. - Env vars reached the client — a missing value usually means the
NEXT_PUBLIC_prefix is absent or the app was not rebuilt after editing.env.local.NEXT_PUBLIC_values are inlined at build time. - Network — in the browser Network tab, confirm
POSTrequests to${endpoint}/v1/tracesand${endpoint}/v1/logssucceed. A404means the token is wrong or rotated — ingest matches the token at the edge, so an unrecognised token fails route matching instead of returning401/403. A CORS error meansendpointis misconfigured. - No backend correlation — trace headers are same-origin only by default. If your API is on another origin, add it to the propagation allowlist.
Still stuck? Ask the Investigation Agent or contact support.
Next steps
Section titled “Next steps”- Session replay — masking, privacy classes, and upload behavior
- Frontend errors — error sources, event names, and manual capture
- Sampling — lower cost as traffic grows while keeping broken sessions
- SDK reference — every configuration option and the full public API