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.
1// app/layout.tsx
2import Script from "next/script";
3
4export default function RootLayout({ children }: { children: React.ReactNode }) {
5 return (
6 <html lang="en">
7 <head>
8 {/* 1) Load the RUM SDK as early as possible */}
9 <Script
10 id="mw-rum-sdk"
11 src="https://cdnjs.middleware.io/browser/libs/0.0.2/middleware-rum.min.js"
12 strategy="beforeInteractive"
13 crossOrigin="anonymous"
14 />
15 {/* 2) Initialise RUM (inline) */}
16 <Script
17 id="mw-rum-init"
18 strategy="beforeInteractive"
19 dangerouslySetInnerHTML={{
20 __html: `
21 if (window.Middleware) {
22 Middleware.track({
23 projectName: "my-application",
24 serviceName: "mw-application",
25 accountKey: "your-account-token",
26 target: "https://<UID>.middleware.io",
27 env: "production",
28 defaultAttributes: { "app.version": "1.0.0" }
29 });
30 }
31 `,
32 }}
33 />
34 {/* Optional: consent-gated init (see "Consent" below) */}
35 </head>
36 <body>{children}</body>
37 </html>
38 );
39}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.
1// pages/_document.tsx
2import Document, { Html, Head, Main, NextScript } from "next/document";
3import Script from "next/script";
4
5export default class MyDocument extends Document {
6 render() {
7 return (
8 <Html lang="en">
9 <Head>
10 <Script
11 id="mw-rum-sdk"
12 src="https://cdnjs.middleware.io/browser/libs/0.0.2/middleware-rum.min.js"
13 strategy="beforeInteractive"
14 crossOrigin="anonymous"
15 />
16 <Script
17 id="mw-rum-init"
18 strategy="beforeInteractive"
19 dangerouslySetInnerHTML={{
20 __html: `
21 if (window.Middleware) {
22 Middleware.track({
23 projectName: "my-application",
24 serviceName: "mw-application",
25 accountKey: "your-account-token",
26 target: "https://<UID>.middleware.io",
27 env: "production",
28 defaultAttributes: { "app.version": "1.0.0" }
29 });
30 }
31 `,
32 }}
33 />
34 </Head>
35 <body>
36 <Main />
37 <NextScript />
38 </body>
39 </Html>
40 );
41 }
42}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.
1// add inside the init block shown above
2recording: "1", // default full session capture; set "0" to disable recording
3recordingOptions: {
4 maskAllInputs: true, // mask form values as ***
5 maskTextSelector: ".pii, [data-pii]",// mask likely-PII text
6 blockSelector: ".auth-widget, .payment-form" // exclude sensitive regions
7}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.
1// Replace the inline init in App/Pages with this consent-gated version:
2dangerouslySetInnerHTML={{
3 __html: `
4 (function() {
5 function hasAnalyticsConsent() {
6 return document.cookie.includes("consent_analytics=true");
7 }
8 if (hasAnalyticsConsent() && window.Middleware) {
9 Middleware.track({
10 projectName: "my-application",
11 serviceName: "mw-application",
12 accountKey: "your-account-token",
13 target: "https://<UID>.middleware.io",
14 env: "production",
15 defaultAttributes: { "app.version": "1.0.0" }
16 // recording: "0" // example: disable only the replay if needed
17 });
18 }
19 })();
20 `,
21}}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.
1// app/components/ClientAttributes.tsx
2"use client";
3import { useEffect } from "react";
4
5export default function ClientAttributes(props: { user?: { name?: string; email?: string; role?: string } }) {
6 useEffect(() => {
7 if (window.Middleware && props.user) {
8 window.Middleware.setAttributes({
9 name: props.user.name,
10 email: props.user.email,
11 user_role: props.user.role,
12 });
13 }
14 }, [props.user]);
15 return null;
16}Include <ClientAttributes user={currentUser} /> in your app shell when identity is available.
Add Custom Logs
Use SDK helpers anywhere (components, error boundaries, services):
1if (window.Middleware) {
2 Middleware.error("Your error message");
3 Middleware.error(new Error("Your error message"));
4 Middleware.info("info message");
5 Middleware.warn("warn message");
6 Middleware.debug("debug message");
7}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:
1// add inside your init block
2tracePropagationTargets: [/localhost:3000/i, /api\.domain\.com/i],
3tracePropagationFormat: "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:1Content-Security-Policy: 2default-src 'self'; 3script-src 'self' https://cdnjs.middleware.io 'unsafe-inline'; 4connect-src 'self' https://<UID>.middleware.io; 5img-src 'self' data:; 6style-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.