Browser RUM - NextJS
Install
When using Next.js, you don’t edit a raw index.html. Load the RUM browser SDK with next/script, then initialise early so views/resources/errors are captured reliably.
Use beforeInteractive for the SDK so it loads before any Next.js code. In the App Router, place it in app/layout.tsx. In the Pages Router, place it in pages/_document.tsx. If you put beforeInteractive elsewhere, Next.js raises “No Before Interactive Script Outside Document”.
App Router
Define the structure in app/layout.tsx and load the SDK + init there.
// app/layout.tsx
import Script from "next/script";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
{/* 1) Load the RUM SDK as early as possible */}
<Script
id="mw-rum-sdk"
src="https://cdnjs.middleware.io/browser/libs/0.0.2/middleware-rum.min.js"
strategy="beforeInteractive"
crossOrigin="anonymous"
/>
{/* 2) Initialise RUM (inline) */}
<Script
id="mw-rum-init"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: `
if (window.Middleware) {
Middleware.track({
projectName: "my-application",
serviceName: "mw-application",
accountKey: "your-account-token",
target: "https://<UID>.middleware.io",
env: "production",
defaultAttributes: { "app.version": "1.0.0" }
});
}
`,
}}
/>
{/* Optional: consent-gated init (see "Consent" below) */}
</head>
<body>{children}</body>
</html>
);
}Soft navigations (SPA routes). Next.js updates the URL via the History API; the SDK treats these as new views, so route changes are measured like page loads. No extra code is needed if the URL updates normally.
Pages Router
Use pages/_document.tsx for beforeInteractive (global scripts). If you prefer putting code in _app.tsx, switch strategy to afterInteractive.
// pages/_document.tsx
import Document, { Html, Head, Main, NextScript } from "next/document";
import Script from "next/script";
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
<Script
id="mw-rum-sdk"
src="https://cdnjs.middleware.io/browser/libs/0.0.2/middleware-rum.min.js"
strategy="beforeInteractive"
crossOrigin="anonymous"
/>
<Script
id="mw-rum-init"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: `
if (window.Middleware) {
Middleware.track({
projectName: "my-application",
serviceName: "mw-application",
accountKey: "your-account-token",
target: "https://<UID>.middleware.io",
env: "production",
defaultAttributes: { "app.version": "1.0.0" }
});
}
`,
}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}If you initialise in pages/_app.tsx, use strategy="afterInteractive" to avoid the beforeInteractive error and keep behaviour correct.
Privacy quick-start (Session Recording)
Tighten privacy at init using recordingOptions. These affect Session Recording only; masking happens client-side before data is sent.
// add inside the init block shown above
recording: "1", // default full session capture; set "0" to disable recording
recordingOptions: {
maskAllInputs: true, // mask form values as ***
maskTextSelector: ".pii, [data-pii]",// mask likely-PII text
blockSelector: ".auth-widget, .payment-form" // exclude sensitive regions
}See Session Recording Privacy for the full option matrix (blockClass, ignoreSelector, custom mask functions). (This is additive to your current config.)
Consent (GDPR/CCPA)
Gate initialisation on your CMP signal. Example: only start RUM if a cookie indicates analytics consent.
// Replace the inline init in App/Pages with this consent-gated version:
dangerouslySetInnerHTML={{
__html: `
(function() {
function hasAnalyticsConsent() {
return document.cookie.includes("consent_analytics=true");
}
if (hasAnalyticsConsent() && window.Middleware) {
Middleware.track({
projectName: "my-application",
serviceName: "mw-application",
accountKey: "your-account-token",
target: "https://<UID>.middleware.io",
env: "production",
defaultAttributes: { "app.version": "1.0.0" }
// recording: "0" // example: disable only the replay if needed
});
}
})();
`,
}}Add User Information (React patterns)
Update attributes after login or when identity changes. In App Router, use a small Client Component; in Pages Router, a useEffect in _app.
// app/components/ClientAttributes.tsx
"use client";
import { useEffect } from "react";
export default function ClientAttributes(props: { user?: { name?: string; email?: string; role?: string } }) {
useEffect(() => {
if (window.Middleware && props.user) {
window.Middleware.setAttributes({
name: props.user.name,
email: props.user.email,
user_role: props.user.role,
});
}
}, [props.user]);
return null;
}Include <ClientAttributes user={currentUser} /> in your app shell when identity is available.
Add Custom Logs
Use SDK helpers anywhere (components, error boundaries, services):
if (window.Middleware) {
Middleware.error("Your error message");
Middleware.error(new Error("Your error message"));
Middleware.info("info message");
Middleware.warn("warn message");
Middleware.debug("debug message");
}Data appears on the RUM Dashboard within minutes.
Distributed Tracing (RUM ↔ APM)
Correlate frontend views/resources with backend traces. Ensure a Middleware APM agent is installed. Then add propagation config:
// add inside your init block
tracePropagationTargets: [/localhost:3000/i, /api\.domain\.com/i],
tracePropagationFormat: "b3" // or "w3c" to match your backend APMtracePropagationTargetsare RegExp that match the browser-called hosts.- Some backends require enabling matching propagators (e.g.,
OTEL_PROPAGATORS=b3). This mirrors standard RUM↔APM correlation guidance.
Troubleshooting
Nothing shows up (SPA):
- Ensure the router updates the URL (App
Router/next/navigationdoes; memory-only routers in tests won’t emit new views).
“No Before Interactive Script Outside Document”:
- Place
beforeInteractivescripts inapp/layout.tsx(App Router) orpages/_document.tsx(Pages Router). Otherwise, change toafterInteractive. (Next.js)
CSP / corporate network blocks:
- Allow the SDK host in
script-srcand your ingesttargetinconnect-src:Content-Security-Policy: default-src 'self'; script-src 'self' https://cdnjs.middleware.io 'unsafe-inline'; connect-src 'self' https://<UID>.middleware.io; img-src 'self' data:; style-src 'self' 'unsafe-inline';
Re-validate CSP after SDK/CDN changes.
Minified stack traces:
- Upload source maps and set
"app.version"indefaultAttributesto match your build; correlate errors to releases on the dashboard.
Script strategy tips:
beforeInteractiveis for critical, global scripts;afterInteractiveloads after some hydration;lazyOnloadduring idle. Pick the lightest strategy that meets your needs.
Need assistance or want to learn more about Middleware? Contact our support team at [email protected] or join our Slack channel.