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 APM
tracePropagationTargets
are 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/navigation
does; memory-only routers in tests won’t emit new views).
“No Before Interactive Script Outside Document”:
- Place
beforeInteractive
scripts 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-src
and your ingesttarget
inconnect-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"
indefaultAttributes
to match your build; correlate errors to releases on the dashboard.
Script strategy tips:
beforeInteractive
is for critical, global scripts;afterInteractive
loads after some hydration;lazyOnload
during 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.