Skip to content

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.

  • 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).
Terminal window
npm install @osuite/rum
# or
pnpm add @osuite/rum

React is already present in a Next.js app, so the error boundary from @osuite/rum/react needs no extra install.

  1. Create a client component that starts the SDK

    Mark it 'use client' and call osuite.init inside useEffect so 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;
    }
  2. 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>
    );
    }
  3. 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=production
    NEXT_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.

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.

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:

app/rum-boundary.tsx
'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.

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.

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.

No data in Osuite after a few minutes? Work through these checks.

  • Init ran in the browser — confirm the OsuiteRum component is mounted in the layout and the effect fired. Set debug: true (see the debug option) to print init and transport diagnostics to console.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 POST requests to ${endpoint}/v1/traces and ${endpoint}/v1/logs succeed. A 404 means the token is wrong or rotated — ingest matches the token at the edge, so an unrecognised token fails route matching instead of returning 401/403. A CORS error means endpoint is 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.

  • 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