From fda52cb75b7bedcf5b89720326e86a9e2e7bb3ca Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Tue, 17 Mar 2026 15:26:58 +0100 Subject: [PATCH 01/13] Add error boundary to Rum-Nextjs --- package.json | 2 +- packages/rum-nextjs/package.json | 8 +- .../src/domain/error/errorBoundary.spec.tsx | 98 +++++++++++++++++++ .../src/domain/error/errorBoundary.ts | 22 +++++ packages/rum-nextjs/src/entries/main.ts | 2 + packages/rum-react/.npmignore | 1 + packages/rum-react/internal.d.ts | 1 + .../src/domain/error/errorBoundary.ts | 2 +- test/apps/nextjs/yarn.lock | 49 +++++----- yarn.lock | 7 +- 10 files changed, 165 insertions(+), 27 deletions(-) create mode 100644 packages/rum-nextjs/src/domain/error/errorBoundary.spec.tsx create mode 100644 packages/rum-nextjs/src/domain/error/errorBoundary.ts create mode 100644 packages/rum-react/internal.d.ts diff --git a/package.json b/package.json index 5ccb8f213f..60c9245f6c 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "build:apps": "node scripts/build/build-test-apps.ts", "build:docs:json": "typedoc --logLevel Verbose --json ./docs.json", "build:docs:html": "typedoc --out ./docs", - "pack": "yarn workspaces foreach --all --parallel --include '@datadog/*' exec yarn pack", + "pack": "yarn workspaces foreach --all --parallel --topological --include '@datadog/*' exec yarn pack", "format": "prettier --check .", "lint": "NODE_OPTIONS='--max-old-space-size=4096' eslint .", "typecheck": "tsc -b --noEmit true", diff --git a/packages/rum-nextjs/package.json b/packages/rum-nextjs/package.json index c16abf2134..4231a5eae5 100644 --- a/packages/rum-nextjs/package.json +++ b/packages/rum-nextjs/package.json @@ -14,10 +14,14 @@ "@datadog/browser-rum-core": "6.31.0" }, "peerDependencies": { + "@datadog/browser-rum-react": ">=6.0.0", "next": ">=13.0.0", "react": ">=18.0.0" }, "peerDependenciesMeta": { + "@datadog/browser-rum-react": { + "optional": true + }, "next": { "optional": true }, @@ -37,8 +41,10 @@ "access": "public" }, "devDependencies": { + "@datadog/browser-rum-react": "6.31.0", "@types/react": "19.2.11", "next": "15.5.10", - "react": "19.2.4" + "react": "19.2.4", + "react-dom": "19.2.4" } } diff --git a/packages/rum-nextjs/src/domain/error/errorBoundary.spec.tsx b/packages/rum-nextjs/src/domain/error/errorBoundary.spec.tsx new file mode 100644 index 0000000000..cef0c1a690 --- /dev/null +++ b/packages/rum-nextjs/src/domain/error/errorBoundary.spec.tsx @@ -0,0 +1,98 @@ +import React, { act } from 'react' + +import { disableJasmineUncaughtExceptionTracking, ignoreConsoleLogs, registerCleanupTask } from '@datadog/browser-core/test' +import type { RumInitConfiguration, RumPublicApi } from '@datadog/browser-rum-core' +import { appendComponent } from '../../../../rum-react/test/appendComponent' +import { initReactOldBrowsersSupport } from '../../../../rum-react/test/reactOldBrowsersSupport' +import { nextjsPlugin, resetNextjsPlugin } from '../nextjsPlugin' +import type { ErrorBoundaryFallback } from './errorBoundary' +import { ErrorBoundary } from './errorBoundary' + +type FallbackFunctionComponent = Extract any> + +function initializeNextjsPlugin() { + const addErrorSpy = jasmine.createSpy() + const publicApi = { startView: jasmine.createSpy() } as unknown as RumPublicApi + const plugin = nextjsPlugin() + plugin.onInit({ publicApi, initConfiguration: {} as RumInitConfiguration }) + plugin.onRumStart({ addError: addErrorSpy }) + return { addErrorSpy } +} + +describe('ErrorBoundary', () => { + beforeEach(() => { + ignoreConsoleLogs('error', 'Error: error') + disableJasmineUncaughtExceptionTracking() + initReactOldBrowsersSupport() + resetNextjsPlugin() + registerCleanupTask(() => resetNextjsPlugin()) + }) + + it('renders children', () => { + const container = appendComponent( null}>bar) + expect(container.innerHTML).toBe('bar') + }) + + it('renders the fallback when an error occurs', () => { + const fallbackSpy = jasmine.createSpy().and.returnValue('fallback') + const ComponentSpy = jasmine.createSpy().and.throwError(new Error('error')) + const container = appendComponent( + + + + ) + + expect(fallbackSpy).toHaveBeenCalled() + fallbackSpy.calls.all().forEach(({ args }) => { + expect(args[0]).toEqual({ + error: new Error('error'), + resetError: jasmine.any(Function), + }) + }) + expect(container.innerHTML).toBe('fallback') + }) + + it('resets the error when resetError is called', () => { + const fallbackSpy = jasmine.createSpy().and.returnValue('fallback') + const ComponentSpy = jasmine.createSpy().and.throwError(new Error('error')) + const container = appendComponent( + + + + ) + + ComponentSpy.and.returnValue('bar') + + const { resetError } = fallbackSpy.calls.mostRecent().args[0] + act(() => { + resetError() + }) + + expect(container.innerHTML).toBe('bar') + }) + + it('reports the error through addNextjsError', () => { + const { addErrorSpy } = initializeNextjsPlugin() + const originalError = new Error('error') + const ComponentSpy = jasmine.createSpy().and.throwError(originalError) + ;(ComponentSpy as any).displayName = 'ComponentSpy' + + appendComponent( + null}> + + + ) + + expect(addErrorSpy).toHaveBeenCalledOnceWith( + jasmine.objectContaining({ + error: originalError, + handlingStack: jasmine.any(String), + startClocks: jasmine.any(Object), + context: jasmine.objectContaining({ + framework: 'nextjs', + }), + componentStack: jasmine.stringContaining('ComponentSpy'), + }) + ) + }) +}) diff --git a/packages/rum-nextjs/src/domain/error/errorBoundary.ts b/packages/rum-nextjs/src/domain/error/errorBoundary.ts new file mode 100644 index 0000000000..67ad094d54 --- /dev/null +++ b/packages/rum-nextjs/src/domain/error/errorBoundary.ts @@ -0,0 +1,22 @@ +'use client' +import { createErrorBoundary } from '@datadog/browser-rum-react/internal' +export type { ErrorBoundaryFallback, ErrorBoundaryProps } from '@datadog/browser-rum-react/internal' +import { addNextjsError } from './addNextjsError' + +/** + * ErrorBoundary component to report React errors to Datadog using the Next.js error context. + * + * For more advanced error handling, you can use the {@link addNextjsError} function. + * + * @category Error + * @example + * ```ts + * import { ErrorBoundary } from '@datadog/browser-rum-nextjs' + * + * null}> + * + * + * ``` + */ +// eslint-disable-next-line local-rules/disallow-side-effects +export const ErrorBoundary = createErrorBoundary(addNextjsError) diff --git a/packages/rum-nextjs/src/entries/main.ts b/packages/rum-nextjs/src/entries/main.ts index d6bdb2237f..70f1d03311 100644 --- a/packages/rum-nextjs/src/entries/main.ts +++ b/packages/rum-nextjs/src/entries/main.ts @@ -3,3 +3,5 @@ export type { NextjsPlugin } from '../domain/nextjsPlugin' export { DatadogAppRouter } from '../domain/nextJSRouter/datadogAppRouter' export { DatadogPagesRouter } from '../domain/nextJSRouter/datadogPagesRouter' export { addNextjsError } from '../domain/error/addNextjsError' +export { ErrorBoundary } from '../domain/error/errorBoundary' +export type { ErrorBoundaryFallback, ErrorBoundaryProps } from '../domain/error/errorBoundary' diff --git a/packages/rum-react/.npmignore b/packages/rum-react/.npmignore index 328d63a686..a4fc3af403 100644 --- a/packages/rum-react/.npmignore +++ b/packages/rum-react/.npmignore @@ -4,5 +4,6 @@ !/src/**/* /src/**/*.spec.ts /src/**/*.specHelper.ts +!/internal.d.ts !/internal/* !/react-router-v[6-7]/* diff --git a/packages/rum-react/internal.d.ts b/packages/rum-react/internal.d.ts new file mode 100644 index 0000000000..4f467d8cb2 --- /dev/null +++ b/packages/rum-react/internal.d.ts @@ -0,0 +1 @@ +export * from './src/entries/internal' diff --git a/packages/rum-react/src/domain/error/errorBoundary.ts b/packages/rum-react/src/domain/error/errorBoundary.ts index 1d679ce566..453ad47973 100644 --- a/packages/rum-react/src/domain/error/errorBoundary.ts +++ b/packages/rum-react/src/domain/error/errorBoundary.ts @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import type { ErrorInfo } from 'react' import { addReactError } from './addReactError' diff --git a/test/apps/nextjs/yarn.lock b/test/apps/nextjs/yarn.lock index dbd312fc16..d504c12dac 100644 --- a/test/apps/nextjs/yarn.lock +++ b/test/apps/nextjs/yarn.lock @@ -6,45 +6,48 @@ __metadata: cacheKey: 10c0 "@datadog/browser-core@file:../../../packages/core/package.tgz::locator=nextjs%40workspace%3A.": - version: 6.30.1 - resolution: "@datadog/browser-core@file:../../../packages/core/package.tgz#../../../packages/core/package.tgz::hash=938bcb&locator=nextjs%40workspace%3A." - checksum: 10c0/57c9e38cc77d551614bd166ebbe2c0dd1048866377d0a325a0a486f6a12c99468e570c64ed39dfcd314a2f2895a76d413178a07f8d4f8c5b7226aaf332191b6e + version: 6.31.0 + resolution: "@datadog/browser-core@file:../../../packages/core/package.tgz#../../../packages/core/package.tgz::hash=36bd6a&locator=nextjs%40workspace%3A." + checksum: 10c0/1e3797a56310fde499578396bd2594c4e0ac891610e7d57ddac2164a8c29a97177b3eac938f6a8480192bb31b5d798acaf2ea26de1778047d43c010ff0124dc0 languageName: node linkType: hard "@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz::locator=nextjs%40workspace%3A.": - version: 6.30.1 - resolution: "@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz#../../../packages/rum-core/package.tgz::hash=37de46&locator=nextjs%40workspace%3A." + version: 6.31.0 + resolution: "@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz#../../../packages/rum-core/package.tgz::hash=55eb2b&locator=nextjs%40workspace%3A." dependencies: - "@datadog/browser-core": "npm:6.30.1" - checksum: 10c0/d9abf0dc293c6fee43c1c01894313b19325b7d7a5045f96943a48e14fca1d6ee8ae040781f1801f40b10e668582704ea09d69d88b838d999de5bc4401b20027c + "@datadog/browser-core": "npm:6.31.0" + checksum: 10c0/11b93076a410ef527c70d94d7b3203a3a24e05b665f40ce9b1c2227a30ee7efd99613e02a139be7f94ee1a95ca48cb2bf83e8b6d9f3d5253f7bf37d89f1ea338 languageName: node linkType: hard "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz::locator=nextjs%40workspace%3A.": - version: 6.30.1 - resolution: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz#../../../packages/rum-nextjs/package.tgz::hash=5da15c&locator=nextjs%40workspace%3A." + version: 6.31.0 + resolution: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz#../../../packages/rum-nextjs/package.tgz::hash=fbbfac&locator=nextjs%40workspace%3A." dependencies: - "@datadog/browser-core": "npm:6.30.1" - "@datadog/browser-rum-core": "npm:6.30.1" + "@datadog/browser-core": "npm:6.31.0" + "@datadog/browser-rum-core": "npm:6.31.0" peerDependencies: + "@datadog/browser-rum-react": ">=6.0.0" next: ">=13.0.0" react: ">=18.0.0" peerDependenciesMeta: + "@datadog/browser-rum-react": + optional: true next: optional: true react: optional: true - checksum: 10c0/f60b0d7d24501bcc9c10b3d37abc5db4cf54e0f676dae8a2377033ac0dce93b98acd565b0fb9e0138092097b861277f677c5c9104b56cf1cbc825c3d3b2547c9 + checksum: 10c0/06ffa3b9f798164b9f838c04fa69c011e70273f24f9a07f90fc61da530e7e9e01e9b9ab80c9fa372c67ec9565f78b6c355f3c6f239e51dae4e12137a47699cd1 languageName: node linkType: hard "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz::locator=nextjs%40workspace%3A.": - version: 6.30.1 - resolution: "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz#../../../packages/rum-react/package.tgz::hash=c7f83a&locator=nextjs%40workspace%3A." + version: 6.31.0 + resolution: "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz#../../../packages/rum-react/package.tgz::hash=9ed545&locator=nextjs%40workspace%3A." dependencies: - "@datadog/browser-core": "npm:6.30.1" - "@datadog/browser-rum-core": "npm:6.30.1" + "@datadog/browser-core": "npm:6.31.0" + "@datadog/browser-rum-core": "npm:6.31.0" peerDependencies: react: 18 || 19 react-router: 6 || 7 @@ -60,22 +63,22 @@ __metadata: optional: true react-router-dom: optional: true - checksum: 10c0/996c165116a29c3a7fad114865b72621f5501d062a5343f34a60d22ce52fad6235759eff64906d4bd9b3f25519f53d30b777f697de9b732235a02273fbeca194 + checksum: 10c0/aed46f0c89e24df14c29e6670911eea459824e1ade4c795e68d38082f7b07866f55ff56dd22621e704356d63c643f128f17ee0354177d832241ef41399bfbc77 languageName: node linkType: hard "@datadog/browser-rum@file:../../../packages/rum/package.tgz::locator=nextjs%40workspace%3A.": - version: 6.30.1 - resolution: "@datadog/browser-rum@file:../../../packages/rum/package.tgz#../../../packages/rum/package.tgz::hash=956b2b&locator=nextjs%40workspace%3A." + version: 6.31.0 + resolution: "@datadog/browser-rum@file:../../../packages/rum/package.tgz#../../../packages/rum/package.tgz::hash=b23186&locator=nextjs%40workspace%3A." dependencies: - "@datadog/browser-core": "npm:6.30.1" - "@datadog/browser-rum-core": "npm:6.30.1" + "@datadog/browser-core": "npm:6.31.0" + "@datadog/browser-rum-core": "npm:6.31.0" peerDependencies: - "@datadog/browser-logs": 6.30.1 + "@datadog/browser-logs": 6.31.0 peerDependenciesMeta: "@datadog/browser-logs": optional: true - checksum: 10c0/56030d55fd6301da366d2b1e12609ae7a561f2a59f381939a08cf50893043cc571d110619b2ea50a3f9ca508db8f80ede3f016c25ac0420d1ba850df5b892899 + checksum: 10c0/5eeaf5251ce0207c45d6782b3e219f8366fbce0b60bddd8fa35473a3dfc84a97fc6e2fb686033d9c985ebade414ef432cd18f17e753c694c469a3e9a6ac4aad5 languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index 9946bbf622..bae6eef22a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -307,13 +307,18 @@ __metadata: dependencies: "@datadog/browser-core": "npm:6.31.0" "@datadog/browser-rum-core": "npm:6.31.0" + "@datadog/browser-rum-react": "npm:6.31.0" "@types/react": "npm:19.2.11" next: "npm:15.5.10" react: "npm:19.2.4" + react-dom: "npm:19.2.4" peerDependencies: + "@datadog/browser-rum-react": ">=6.0.0" next: ">=13.0.0" react: ">=18.0.0" peerDependenciesMeta: + "@datadog/browser-rum-react": + optional: true next: optional: true react: @@ -321,7 +326,7 @@ __metadata: languageName: unknown linkType: soft -"@datadog/browser-rum-react@workspace:packages/rum-react": +"@datadog/browser-rum-react@npm:6.31.0, @datadog/browser-rum-react@workspace:packages/rum-react": version: 0.0.0-use.local resolution: "@datadog/browser-rum-react@workspace:packages/rum-react" dependencies: From 48f67696d717d66673922281d41a565760816ce8 Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Tue, 17 Mar 2026 16:27:36 +0100 Subject: [PATCH 02/13] Add e2e tests --- test/apps/nextjs/pages/_error.tsx | 20 +++ .../nextjs/pages/pages-router/error-test.tsx | 35 +++++ test/apps/nextjs/pages/pages-router/index.tsx | 3 + .../nextjs/pages/pages-router/throw-error.tsx | 7 + test/e2e/playwright.base.config.ts | 2 +- test/e2e/scenario/nextjsPlugin.scenario.ts | 132 +++++++++++------- 6 files changed, 147 insertions(+), 52 deletions(-) create mode 100644 test/apps/nextjs/pages/_error.tsx create mode 100644 test/apps/nextjs/pages/pages-router/error-test.tsx create mode 100644 test/apps/nextjs/pages/pages-router/throw-error.tsx diff --git a/test/apps/nextjs/pages/_error.tsx b/test/apps/nextjs/pages/_error.tsx new file mode 100644 index 0000000000..940888aee2 --- /dev/null +++ b/test/apps/nextjs/pages/_error.tsx @@ -0,0 +1,20 @@ +import { addNextjsError } from '@datadog/browser-rum-nextjs' +import { useEffect } from 'react' + +function ErrorPage({ err }: { err?: Error & { statusCode?: number } }) { + useEffect(() => { + if (err) addNextjsError(err) + }, [err]) + return ( +
+

Something went wrong

+
+ ) +} + +ErrorPage.getInitialProps = ({ res, err }: { res?: { statusCode: number }; err?: Error }) => { + const statusCode = res?.statusCode ?? (err as any)?.statusCode ?? 404 + return { statusCode, err } +} + +export default ErrorPage \ No newline at end of file diff --git a/test/apps/nextjs/pages/pages-router/error-test.tsx b/test/apps/nextjs/pages/pages-router/error-test.tsx new file mode 100644 index 0000000000..fa043ecbcf --- /dev/null +++ b/test/apps/nextjs/pages/pages-router/error-test.tsx @@ -0,0 +1,35 @@ +import { ErrorBoundary } from '@datadog/browser-rum-nextjs' +import type { ErrorBoundaryFallback } from '@datadog/browser-rum-nextjs' +import { useState } from 'react' +import Link from 'next/link' + +function ErrorThrower() { + const [shouldThrow, setShouldThrow] = useState(false) + if (shouldThrow) throw new Error('Pages Router error from NextjsErrorBoundary') + return ( + + ) +} + +const ErrorFallback: ErrorBoundaryFallback = ({ error, resetError }) => ( +
+

{error.message}

+ +
+) + +export default function ErrorTestPage() { + return ( +
+ ← Back to Home +

Error Test

+ + + +
+ ) +} \ No newline at end of file diff --git a/test/apps/nextjs/pages/pages-router/index.tsx b/test/apps/nextjs/pages/pages-router/index.tsx index cc03d59a47..2d31b81838 100644 --- a/test/apps/nextjs/pages/pages-router/index.tsx +++ b/test/apps/nextjs/pages/pages-router/index.tsx @@ -11,6 +11,9 @@ export default function HomePage() {
  • Go to Guides 123
  • +
  • + Go to Error Test +
  • ) diff --git a/test/apps/nextjs/pages/pages-router/throw-error.tsx b/test/apps/nextjs/pages/pages-router/throw-error.tsx new file mode 100644 index 0000000000..ff3b0d9252 --- /dev/null +++ b/test/apps/nextjs/pages/pages-router/throw-error.tsx @@ -0,0 +1,7 @@ +export default function ThrowErrorPage() { + return
    This page should not render
    +} + +export function getServerSideProps() { + throw new Error('SSR error from pages router') +} diff --git a/test/e2e/playwright.base.config.ts b/test/e2e/playwright.base.config.ts index 3051897e5c..4461140e8e 100644 --- a/test/e2e/playwright.base.config.ts +++ b/test/e2e/playwright.base.config.ts @@ -48,7 +48,7 @@ export const config: Config = { name: 'nextjs app router', stdout: 'pipe' as const, cwd: path.join(__dirname, '../apps/nextjs'), - command: isLocal ? 'yarn dev' : 'yarn start', + command: 'yarn start', wait: { stdout: /- Local:\s+http:\/\/localhost:(?\d+)/, }, diff --git a/test/e2e/scenario/nextjsPlugin.scenario.ts b/test/e2e/scenario/nextjsPlugin.scenario.ts index 0f718e0560..9201e1b5bb 100644 --- a/test/e2e/scenario/nextjsPlugin.scenario.ts +++ b/test/e2e/scenario/nextjsPlugin.scenario.ts @@ -14,6 +14,7 @@ const routerConfigs = [ router: 'pages' as const, viewPrefix: '/pages-router', homeUrlPattern: /\/pages-router(\?|$)/, + clientErrorMessage: 'Pages Router error from NextjsErrorBoundary', }, ] @@ -200,69 +201,98 @@ test.describe('nextjs - router', () => { }) test.describe('nextjs - errors', () => { - const { name, viewPrefix, clientErrorMessage, router } = routerConfigs[0] - - test.describe(name, () => { - createTest('should report client-side error') - .withRum() - .withNextjsApp(router) - .run(async ({ page, flushEvents, intakeRegistry, withBrowserLogs }) => { - await page.click('text=Go to Error Test') - await page.waitForURL(`**${viewPrefix}/error-test`) + routerConfigs.forEach(({ name, router, viewPrefix, clientErrorMessage }) => { + test.describe(name, () => { + createTest('should report client-side error') + .withRum() + .withNextjsApp(router) + .run(async ({ page, flushEvents, intakeRegistry, withBrowserLogs }) => { + await page.click('text=Go to Error Test') + await page.waitForURL(`**${viewPrefix}/error-test`) - await page.click('[data-testid="trigger-error"]') - await page.waitForSelector('[data-testid="error-boundary"]') + await page.click('[data-testid="trigger-error"]') + await page.waitForSelector('[data-testid="error-boundary"]') - await flushEvents() + await flushEvents() - // React StrictMode double-fires useEffect in dev mode, so we may get 2 errors - const customErrors = intakeRegistry.rumErrorEvents.filter((e) => e.error.source === 'custom') - expect(customErrors.length).toBeGreaterThanOrEqual(1) - expect(customErrors[0].error.message).toBe(clientErrorMessage) - expect(customErrors[0].error.handling_stack).toBeDefined() + const customErrors = intakeRegistry.rumErrorEvents.filter((e) => e.error.source === 'custom') + expect(customErrors).toHaveLength(1) + expect(customErrors[0].error.message).toBe(clientErrorMessage) + expect(customErrors[0].error.handling_stack).toBeDefined() + expect(customErrors[0].context).toMatchObject({ framework: 'nextjs' }) - withBrowserLogs((browserLogs) => { - expect(browserLogs.length).toBeGreaterThan(0) + withBrowserLogs((browserLogs) => { + expect(browserLogs.length).toBeGreaterThan(0) + }) }) - }) - createTest('should report a server error with digest via addNextjsError') - .withRum() - .withNextjsApp(router) - .run(async ({ page, flushEvents, intakeRegistry, withBrowserLogs }) => { - await page.click('text=Go to Server Error') - await page.waitForSelector('[data-testid="error-boundary"]') + if (router === 'app') { + createTest('should report a server error with digest via addNextjsError') + .withRum() + .withNextjsApp(router) + .run(async ({ page, flushEvents, intakeRegistry, withBrowserLogs }) => { + await page.click('text=Go to Server Error') + await page.waitForSelector('[data-testid="error-boundary"]') - await flushEvents() + await flushEvents() - // React StrictMode double-fires useEffect in dev mode, so we may get 2 errors - const customErrors = intakeRegistry.rumErrorEvents.filter((e) => e.error.source === 'custom') - expect(customErrors.length).toBeGreaterThanOrEqual(1) - expect(customErrors[0].error.handling_stack).toBeDefined() - expect((customErrors[0].context?.nextjs as { digest: string }).digest).toBeDefined() + const customErrors = intakeRegistry.rumErrorEvents.filter((e) => e.error.source === 'custom') + expect(customErrors).toHaveLength(1) + expect(customErrors[0].error.handling_stack).toBeDefined() + expect(customErrors[0].context).toMatchObject({ + framework: 'nextjs', + nextjs: { digest: expect.any(String) }, + }) + + withBrowserLogs((browserLogs) => { + expect(browserLogs.length).toBeGreaterThan(0) + }) + }) - withBrowserLogs((browserLogs) => { - expect(browserLogs.length).toBeGreaterThan(0) - }) - }) + createTest('should report global error via global-error.tsx') + .withRum() + .withNextjsApp(router) + .run(async ({ page, flushEvents, intakeRegistry, withBrowserLogs }) => { + await page.click('text=Go to Global Error') + await page.waitForSelector('[data-testid="global-error-boundary"]') - createTest('should report global error via global-error.tsx') - .withRum() - .withNextjsApp(router) - .run(async ({ page, flushEvents, intakeRegistry, withBrowserLogs }) => { - await page.click('text=Go to Global Error') - await page.waitForSelector('[data-testid="global-error-boundary"]') + await flushEvents() + + const customErrors = intakeRegistry.rumErrorEvents.filter((e) => e.error.source === 'custom') + expect(customErrors).toHaveLength(1) + expect(customErrors[0].error.handling_stack).toBeDefined() + expect(customErrors[0].context).toMatchObject({ framework: 'nextjs' }) - await flushEvents() + withBrowserLogs((browserLogs) => { + expect(browserLogs.length).toBeGreaterThan(0) + }) + }) + } - // React StrictMode double-fires useEffect in dev mode, so we may get 2 errors - const customErrors = intakeRegistry.rumErrorEvents.filter((e) => e.error.source === 'custom') - expect(customErrors.length).toBeGreaterThanOrEqual(1) - expect(customErrors[0].error.handling_stack).toBeDefined() + if (router === 'pages') { + createTest('should report a server error via _error page') + .withRum() + .withNextjsApp(router) + .run(async ({ page, flushEvents, intakeRegistry, withBrowserLogs, baseUrl }) => { + // Use page.goto() (not a Link click) so that rum-config params from baseUrl are + // preserved on the hard reload Next.js performs when getServerSideProps throws. + const errorPageUrl = new URL(baseUrl) + errorPageUrl.pathname = '/pages-router/throw-error' + await page.goto(errorPageUrl.href) + await page.waitForSelector('[data-testid="pages-error-page"]') - withBrowserLogs((browserLogs) => { - expect(browserLogs.length).toBeGreaterThan(0) - }) - }) + await flushEvents() + + const customErrors = intakeRegistry.rumErrorEvents.filter((e) => e.error.source === 'custom') + expect(customErrors).toHaveLength(1) + expect(customErrors[0].error.handling_stack).toBeDefined() + expect(customErrors[0].context).toMatchObject({ framework: 'nextjs' }) + + withBrowserLogs((browserLogs) => { + expect(browserLogs.length).toBeGreaterThan(0) + }) + }) + } + }) }) }) From 3cf211b4f7d10434e840c86494b40c99a30a0fb1 Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Tue, 17 Mar 2026 16:42:52 +0100 Subject: [PATCH 03/13] Disallow side effect from React, CI fixes --- eslint-local-rules/disallowSideEffects.js | 1 + packages/rum-nextjs/src/domain/error/errorBoundary.spec.tsx | 6 +++++- test/apps/nextjs/pages/_error.tsx | 2 +- test/apps/nextjs/pages/pages-router/error-test.tsx | 2 +- test/apps/nextjs/yarn.lock | 4 ++-- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/eslint-local-rules/disallowSideEffects.js b/eslint-local-rules/disallowSideEffects.js index b9e7390326..2ade75eb10 100644 --- a/eslint-local-rules/disallowSideEffects.js +++ b/eslint-local-rules/disallowSideEffects.js @@ -35,6 +35,7 @@ const pathsWithSideEffect = new Set([ const packagesWithoutSideEffect = new Set([ '@datadog/browser-core', '@datadog/browser-rum-core', + '@datadog/browser-rum-react/internal', 'react', 'react-router-dom', 'vue', diff --git a/packages/rum-nextjs/src/domain/error/errorBoundary.spec.tsx b/packages/rum-nextjs/src/domain/error/errorBoundary.spec.tsx index cef0c1a690..5961e41a4d 100644 --- a/packages/rum-nextjs/src/domain/error/errorBoundary.spec.tsx +++ b/packages/rum-nextjs/src/domain/error/errorBoundary.spec.tsx @@ -1,6 +1,10 @@ import React, { act } from 'react' -import { disableJasmineUncaughtExceptionTracking, ignoreConsoleLogs, registerCleanupTask } from '@datadog/browser-core/test' +import { + disableJasmineUncaughtExceptionTracking, + ignoreConsoleLogs, + registerCleanupTask, +} from '@datadog/browser-core/test' import type { RumInitConfiguration, RumPublicApi } from '@datadog/browser-rum-core' import { appendComponent } from '../../../../rum-react/test/appendComponent' import { initReactOldBrowsersSupport } from '../../../../rum-react/test/reactOldBrowsersSupport' diff --git a/test/apps/nextjs/pages/_error.tsx b/test/apps/nextjs/pages/_error.tsx index 940888aee2..e22991531f 100644 --- a/test/apps/nextjs/pages/_error.tsx +++ b/test/apps/nextjs/pages/_error.tsx @@ -17,4 +17,4 @@ ErrorPage.getInitialProps = ({ res, err }: { res?: { statusCode: number }; err?: return { statusCode, err } } -export default ErrorPage \ No newline at end of file +export default ErrorPage diff --git a/test/apps/nextjs/pages/pages-router/error-test.tsx b/test/apps/nextjs/pages/pages-router/error-test.tsx index fa043ecbcf..9ec73b34d1 100644 --- a/test/apps/nextjs/pages/pages-router/error-test.tsx +++ b/test/apps/nextjs/pages/pages-router/error-test.tsx @@ -32,4 +32,4 @@ export default function ErrorTestPage() { ) -} \ No newline at end of file +} diff --git a/test/apps/nextjs/yarn.lock b/test/apps/nextjs/yarn.lock index d504c12dac..e96ce250a3 100644 --- a/test/apps/nextjs/yarn.lock +++ b/test/apps/nextjs/yarn.lock @@ -23,7 +23,7 @@ __metadata: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz::locator=nextjs%40workspace%3A.": version: 6.31.0 - resolution: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz#../../../packages/rum-nextjs/package.tgz::hash=fbbfac&locator=nextjs%40workspace%3A." + resolution: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz#../../../packages/rum-nextjs/package.tgz::hash=17032c&locator=nextjs%40workspace%3A." dependencies: "@datadog/browser-core": "npm:6.31.0" "@datadog/browser-rum-core": "npm:6.31.0" @@ -38,7 +38,7 @@ __metadata: optional: true react: optional: true - checksum: 10c0/06ffa3b9f798164b9f838c04fa69c011e70273f24f9a07f90fc61da530e7e9e01e9b9ab80c9fa372c67ec9565f78b6c355f3c6f239e51dae4e12137a47699cd1 + checksum: 10c0/bebd9ff41e95c71e996619a66b402d981727180715b82bb469b50af972a6fe10994e54e0fdb007ce845471893047529cef3c0e8146f589daa10274774e9aa919 languageName: node linkType: hard From e0d3dee6d6f1e3d58a58a35da10bac5b93dbe33b Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Tue, 17 Mar 2026 16:51:29 +0100 Subject: [PATCH 04/13] fix rum-react version --- packages/rum-nextjs/package.json | 2 +- test/apps/nextjs/yarn.lock | 6 +++--- yarn.lock | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/rum-nextjs/package.json b/packages/rum-nextjs/package.json index 4231a5eae5..ef4230bcaa 100644 --- a/packages/rum-nextjs/package.json +++ b/packages/rum-nextjs/package.json @@ -14,7 +14,7 @@ "@datadog/browser-rum-core": "6.31.0" }, "peerDependencies": { - "@datadog/browser-rum-react": ">=6.0.0", + "@datadog/browser-rum-react": "6.31.0", "next": ">=13.0.0", "react": ">=18.0.0" }, diff --git a/test/apps/nextjs/yarn.lock b/test/apps/nextjs/yarn.lock index e96ce250a3..bbcb5679c1 100644 --- a/test/apps/nextjs/yarn.lock +++ b/test/apps/nextjs/yarn.lock @@ -23,12 +23,12 @@ __metadata: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz::locator=nextjs%40workspace%3A.": version: 6.31.0 - resolution: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz#../../../packages/rum-nextjs/package.tgz::hash=17032c&locator=nextjs%40workspace%3A." + resolution: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz#../../../packages/rum-nextjs/package.tgz::hash=2d533f&locator=nextjs%40workspace%3A." dependencies: "@datadog/browser-core": "npm:6.31.0" "@datadog/browser-rum-core": "npm:6.31.0" peerDependencies: - "@datadog/browser-rum-react": ">=6.0.0" + "@datadog/browser-rum-react": 6.31.0 next: ">=13.0.0" react: ">=18.0.0" peerDependenciesMeta: @@ -38,7 +38,7 @@ __metadata: optional: true react: optional: true - checksum: 10c0/bebd9ff41e95c71e996619a66b402d981727180715b82bb469b50af972a6fe10994e54e0fdb007ce845471893047529cef3c0e8146f589daa10274774e9aa919 + checksum: 10c0/d20a215db78b031f9ebdaa50e46d4115d73111ebcf8cb93b23be67ef3292f87964fd39cd465ba5f29a8fc00f888b01457d06527f49f4245fed5740e350a5ae8a languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index bae6eef22a..4b3d436cd4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -313,7 +313,7 @@ __metadata: react: "npm:19.2.4" react-dom: "npm:19.2.4" peerDependencies: - "@datadog/browser-rum-react": ">=6.0.0" + "@datadog/browser-rum-react": 6.31.0 next: ">=13.0.0" react: ">=18.0.0" peerDependenciesMeta: From cd405c3946f556680fdfd721eb0327f4dbe36142 Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Tue, 17 Mar 2026 17:21:38 +0100 Subject: [PATCH 05/13] Remove import --- packages/rum-react/src/domain/error/errorBoundary.ts | 2 +- test/apps/nextjs/yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/rum-react/src/domain/error/errorBoundary.ts b/packages/rum-react/src/domain/error/errorBoundary.ts index 453ad47973..1d679ce566 100644 --- a/packages/rum-react/src/domain/error/errorBoundary.ts +++ b/packages/rum-react/src/domain/error/errorBoundary.ts @@ -1,4 +1,4 @@ -import * as React from 'react' +import React from 'react' import type { ErrorInfo } from 'react' import { addReactError } from './addReactError' diff --git a/test/apps/nextjs/yarn.lock b/test/apps/nextjs/yarn.lock index bbcb5679c1..b8c09f5154 100644 --- a/test/apps/nextjs/yarn.lock +++ b/test/apps/nextjs/yarn.lock @@ -44,7 +44,7 @@ __metadata: "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz::locator=nextjs%40workspace%3A.": version: 6.31.0 - resolution: "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz#../../../packages/rum-react/package.tgz::hash=9ed545&locator=nextjs%40workspace%3A." + resolution: "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz#../../../packages/rum-react/package.tgz::hash=bceee8&locator=nextjs%40workspace%3A." dependencies: "@datadog/browser-core": "npm:6.31.0" "@datadog/browser-rum-core": "npm:6.31.0" @@ -63,7 +63,7 @@ __metadata: optional: true react-router-dom: optional: true - checksum: 10c0/aed46f0c89e24df14c29e6670911eea459824e1ade4c795e68d38082f7b07866f55ff56dd22621e704356d63c643f128f17ee0354177d832241ef41399bfbc77 + checksum: 10c0/03bf4d5f3f94b4d5b864b5415332fbd746a3c4700eeeecfabf040a2bb822a93b73e954ab88e1f7ac7ea33fa1c237942d5292dcfa5eb256713b190cc0cd54b53c languageName: node linkType: hard From 676d17005266486209a9ddc15af3846de8d6e4e8 Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Tue, 17 Mar 2026 18:21:35 +0100 Subject: [PATCH 06/13] build --- test/apps/nextjs/yarn.lock | 51 ++++++++++++-------------------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/test/apps/nextjs/yarn.lock b/test/apps/nextjs/yarn.lock index 26bb6380c9..eb95ea4189 100644 --- a/test/apps/nextjs/yarn.lock +++ b/test/apps/nextjs/yarn.lock @@ -6,9 +6,6 @@ __metadata: cacheKey: 10c0 "@datadog/browser-core@file:../../../packages/core/package.tgz::locator=nextjs%40workspace%3A.": - version: 6.31.0 - resolution: "@datadog/browser-core@file:../../../packages/core/package.tgz#../../../packages/core/package.tgz::hash=36bd6a&locator=nextjs%40workspace%3A." - checksum: 10c0/1e3797a56310fde499578396bd2594c4e0ac891610e7d57ddac2164a8c29a97177b3eac938f6a8480192bb31b5d798acaf2ea26de1778047d43c010ff0124dc0 version: 6.31.0 resolution: "@datadog/browser-core@file:../../../packages/core/package.tgz#../../../packages/core/package.tgz::hash=36bd6a&locator=nextjs%40workspace%3A." checksum: 10c0/1e3797a56310fde499578396bd2594c4e0ac891610e7d57ddac2164a8c29a97177b3eac938f6a8480192bb31b5d798acaf2ea26de1778047d43c010ff0124dc0 @@ -17,50 +14,40 @@ __metadata: "@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz::locator=nextjs%40workspace%3A.": version: 6.31.0 - resolution: "@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz#../../../packages/rum-core/package.tgz::hash=55eb2b&locator=nextjs%40workspace%3A." - version: 6.31.0 - resolution: "@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz#../../../packages/rum-core/package.tgz::hash=55eb2b&locator=nextjs%40workspace%3A." + resolution: "@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz#../../../packages/rum-core/package.tgz::hash=cad31e&locator=nextjs%40workspace%3A." dependencies: "@datadog/browser-core": "npm:6.31.0" - checksum: 10c0/11b93076a410ef527c70d94d7b3203a3a24e05b665f40ce9b1c2227a30ee7efd99613e02a139be7f94ee1a95ca48cb2bf83e8b6d9f3d5253f7bf37d89f1ea338 - "@datadog/browser-core": "npm:6.31.0" - checksum: 10c0/11b93076a410ef527c70d94d7b3203a3a24e05b665f40ce9b1c2227a30ee7efd99613e02a139be7f94ee1a95ca48cb2bf83e8b6d9f3d5253f7bf37d89f1ea338 + checksum: 10c0/f57c5d9fdbc7243f373afba6912bb75a90f3b65ab036750a858aa7776ed30b8921e456eff32a02e7dc021c192cd7bc220ea7b124459eacb7685c5c5ba0cca1c5 languageName: node linkType: hard "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz::locator=nextjs%40workspace%3A.": - version: 6.31.0 - resolution: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz#../../../packages/rum-nextjs/package.tgz::hash=fbbfac&locator=nextjs%40workspace%3A." + version: 0.0.0 + resolution: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz#../../../packages/rum-nextjs/package.tgz::hash=9e606a&locator=nextjs%40workspace%3A." dependencies: "@datadog/browser-core": "npm:6.31.0" "@datadog/browser-rum-core": "npm:6.31.0" - "@datadog/browser-core": "npm:6.31.0" - "@datadog/browser-rum-core": "npm:6.31.0" peerDependencies: - "@datadog/browser-rum-react": ">=6.0.0" + "@datadog/browser-rum-react": 6.31.0 next: ">=13.0.0" react: ">=18.0.0" peerDependenciesMeta: - "@datadog/browser-rum-react": - optional: true "@datadog/browser-rum-react": optional: true next: optional: true react: optional: true - checksum: 10c0/06ffa3b9f798164b9f838c04fa69c011e70273f24f9a07f90fc61da530e7e9e01e9b9ab80c9fa372c67ec9565f78b6c355f3c6f239e51dae4e12137a47699cd1 + checksum: 10c0/c76831c738de5a8d7edc63daf07ca5a9ce63412e624c9dc100f234ecb882c158a4b899e02351c1e35d41025768ac454fc5f8d5befe0c8187d07e8d6eea20f19d languageName: node linkType: hard "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz::locator=nextjs%40workspace%3A.": version: 6.31.0 - resolution: "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz#../../../packages/rum-react/package.tgz::hash=9ed545&locator=nextjs%40workspace%3A." + resolution: "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz#../../../packages/rum-react/package.tgz::hash=bceee8&locator=nextjs%40workspace%3A." dependencies: "@datadog/browser-core": "npm:6.31.0" "@datadog/browser-rum-core": "npm:6.31.0" - "@datadog/browser-core": "npm:6.31.0" - "@datadog/browser-rum-core": "npm:6.31.0" peerDependencies: react: 18 || 19 react-router: 6 || 7 @@ -76,28 +63,22 @@ __metadata: optional: true react-router-dom: optional: true - checksum: 10c0/aed46f0c89e24df14c29e6670911eea459824e1ade4c795e68d38082f7b07866f55ff56dd22621e704356d63c643f128f17ee0354177d832241ef41399bfbc77 + checksum: 10c0/03bf4d5f3f94b4d5b864b5415332fbd746a3c4700eeeecfabf040a2bb822a93b73e954ab88e1f7ac7ea33fa1c237942d5292dcfa5eb256713b190cc0cd54b53c languageName: node linkType: hard "@datadog/browser-rum@file:../../../packages/rum/package.tgz::locator=nextjs%40workspace%3A.": version: 6.31.0 - resolution: "@datadog/browser-rum@file:../../../packages/rum/package.tgz#../../../packages/rum/package.tgz::hash=b23186&locator=nextjs%40workspace%3A." - version: 6.31.0 - resolution: "@datadog/browser-rum@file:../../../packages/rum/package.tgz#../../../packages/rum/package.tgz::hash=b23186&locator=nextjs%40workspace%3A." + resolution: "@datadog/browser-rum@file:../../../packages/rum/package.tgz#../../../packages/rum/package.tgz::hash=c2aa74&locator=nextjs%40workspace%3A." dependencies: "@datadog/browser-core": "npm:6.31.0" "@datadog/browser-rum-core": "npm:6.31.0" - "@datadog/browser-core": "npm:6.31.0" - "@datadog/browser-rum-core": "npm:6.31.0" peerDependencies: "@datadog/browser-logs": 6.31.0 - "@datadog/browser-logs": 6.31.0 peerDependenciesMeta: "@datadog/browser-logs": optional: true - checksum: 10c0/5eeaf5251ce0207c45d6782b3e219f8366fbce0b60bddd8fa35473a3dfc84a97fc6e2fb686033d9c985ebade414ef432cd18f17e753c694c469a3e9a6ac4aad5 - checksum: 10c0/5eeaf5251ce0207c45d6782b3e219f8366fbce0b60bddd8fa35473a3dfc84a97fc6e2fb686033d9c985ebade414ef432cd18f17e753c694c469a3e9a6ac4aad5 + checksum: 10c0/135a2cdf999ea9505e08031ae7069f4d6a236a88e6203ef7105c784a77ae23a23fbabc648d793305dcb4b960cbe4a535fab2d688748efb5824137597f4de84b5 languageName: node linkType: hard @@ -437,18 +418,18 @@ __metadata: linkType: hard "baseline-browser-mapping@npm:^2.8.3": - version: 2.10.7 - resolution: "baseline-browser-mapping@npm:2.10.7" + version: 2.10.8 + resolution: "baseline-browser-mapping@npm:2.10.8" bin: baseline-browser-mapping: dist/cli.cjs - checksum: 10c0/fe2988088ede5a2dc7936f0a718d4b500b2a28a7ee3e11e2a3844a9444dd217a95a070c00508d8130da73c4fe576d677b21844bc078f6cd7867fb0e1be60caf0 + checksum: 10c0/a77882e8ac37a900a9a0757b9bf4e50f407829ed17ee7630273ab5da0ae10d9c64ed4c5a1e03afb3490e39713440092417ded4bb856eeb9bd44856eacbd97497 languageName: node linkType: hard "caniuse-lite@npm:^1.0.30001579": - version: 1.0.30001778 - resolution: "caniuse-lite@npm:1.0.30001778" - checksum: 10c0/830042e0a6af0796d3da4d9575f60966b92308c5504577993b618dd196c835d023dbd725fa8b47c33b74c487d75ce01ee3ebd6e7a078714989513110e8ff80e5 + version: 1.0.30001780 + resolution: "caniuse-lite@npm:1.0.30001780" + checksum: 10c0/8a88f39758a228852d6f3ac92362ecb7694b1b2b022f194d8dfe59123ad40a5de6202bf2dff0fe316bb3d5ca9caf316c22056e0da693459c3be2771cde4f4bf9 languageName: node linkType: hard From bb905f7e98dea4256822b0d4d78cc4d0e201437e Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Wed, 18 Mar 2026 19:04:44 +0100 Subject: [PATCH 07/13] add skill --- .../datadog-sdk-event-inspection/SKILL.md | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 .claude/skills/datadog-sdk-event-inspection/SKILL.md diff --git a/.claude/skills/datadog-sdk-event-inspection/SKILL.md b/.claude/skills/datadog-sdk-event-inspection/SKILL.md new file mode 100644 index 0000000000..f409a84c7d --- /dev/null +++ b/.claude/skills/datadog-sdk-event-inspection/SKILL.md @@ -0,0 +1,116 @@ +--- +name: datadog-sdk-event-inspection +description: Use when verifying SDK event payloads in the Browser SDK sandbox, debugging what events are emitted, or validating custom attributes and event fields via chrome_devtools MCP. +--- + +# Datadog Browser SDK — Event Inspection + +## Overview + +The SDK calls `window.__ddBrowserSdkExtensionCallback(msg)` for every event before +batching. Setting this callback captures full event payloads before they reach intake. + +**Core principle:** Set the callback BEFORE the SDK emits the event you want to inspect. + +**Do not use network request interception for this.** Events are not flushed immediately (view events only finalize on navigation/session end). The callback approach is reliable and gives you events the moment they are emitted. + +## When to Use + +- Verifying a change emits the correct event type or fields +- Checking custom attributes or user attributes appear on an event +- Debugging why an action/error/log was or wasn't captured +- Validating SDK behavior in the sandbox (`yarn dev` → `http://localhost:8080`) + +## Setup — Choose Your Injection Path + +```dot +digraph injection { + "Need initial view event\nor events from init()?" [shape=diamond]; + "Path B: CDP pre-load injection" [shape=box]; + "Path A: Console injection" [shape=box]; + "Need initial view event\nor events from init()?" -> "Path B: CDP pre-load injection" [label="yes"]; + "Need initial view event\nor events from init()?" -> "Path A: Console injection" [label="no"]; +} +``` + +### Path A — Console Injection (post-load events only) + +For actions, errors, logs, and events triggered **after** the page has fully loaded. + +Paste in the DevTools console after the page loads: + +```js +window.__ddBrowserSdkExtensionCallback = (msg) => { + console.log('[SDK Event]', JSON.stringify(msg)) +} +``` + +Then trigger the interaction. Read `[SDK Event]` lines from console output. + +### Path B — CDP Pre-load Injection (initial view + events from init) + +The sandbox calls `DD_RUM.init()` synchronously in ``. The initial view event fires during page load — before any post-load console injection runs. + +**Before navigating**, call the `Page.addScriptToEvaluateOnNewDocument` CDP command via the chrome_devtools MCP. This is a protocol-level call — NOT JavaScript to paste in the console. + +The script source to inject: + +```js +window.__ddBrowserSdkExtensionCallback = (msg) => { + console.log('[SDK Event]', JSON.stringify(msg)) +} +``` + +Invoke it via the MCP (exact tool name varies — check available tools, look for `Page.addScriptToEvaluateOnNewDocument` or a generic `execute_cdp_command`): + +``` +Tool: Page.addScriptToEvaluateOnNewDocument (or equivalent) +Params: { source: "window.__ddBrowserSdkExtensionCallback = (msg) => { console.log('[SDK Event]', JSON.stringify(msg)) }" } +``` + +Then navigate to `http://localhost:8080`. Every event — including the initial view — will appear in console logs. + +**Do not inject via console and then reload.** Reloading clears the injected console code. + +## Event Message Structure + +``` +{ type: 'rum', payload: RumEvent } // view, action, error, resource, long_task +{ type: 'logs', payload: LogsEvent } +{ type: 'telemetry', payload: TelemetryEvent } +{ type: 'record', payload: { record, segment } } // Session Replay only +``` + +## Key Fields Quick Reference + +| What to check | Field path | +|---|---| +| Event category | `msg.type` | +| RUM sub-type | `msg.payload.type` | +| User attribute (via `setUser`) | `msg.payload.usr.` | +| Global context attribute (via `addRumGlobalContext`) | `msg.payload.context.` | +| Action name | `msg.payload.action.target.name` | +| Error message | `msg.payload.error.message` | +| View URL | `msg.payload.view.url` | +| Log message | `msg.payload.message` | +| Telemetry status | `msg.payload.telemetry.status` | + +Note: user attributes use `usr` (not `user`) in the serialized payload. + +## Flush Caveat + +**View events are only finalized when the view ends** (navigation or session end). +The initial view event will be updated multiple times as the page runs — earlier emissions are incomplete. + +To get the final view event: trigger a navigation, or call `window.DD_RUM.stopSession()` in the console (note: this permanently ends the current session — only use for debugging). + +## Common Mistakes + +| Mistake | Fix | +|---|---| +| Using network requests to verify events | Events may not be flushed yet; use the callback instead | +| Console injection + reload to capture early events | Reload clears injected code — use Path B (CDP pre-load) | +| Missing the initial view event | Use Path B: `Page.addScriptToEvaluateOnNewDocument` before navigating | +| Seeing no `[SDK Event]` logs after reload | The callback was cleared by reload — re-run Path B setup | +| Incomplete view event data | View updates until view ends — navigate away or call `stopSession()` | +| Looking for `user.plan` in payload | It's `usr.plan` in serialized RUM events | \ No newline at end of file From c7e7ca72edce7983b1ec5e2886780dddcae25dfc Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Wed, 18 Mar 2026 19:13:10 +0100 Subject: [PATCH 08/13] Fix tests --- test/apps/nextjs/app/error-test/error.tsx | 2 +- test/apps/nextjs/app/error-test/page.tsx | 2 +- .../apps/nextjs/app/guides/[...slug]/page.tsx | 2 +- .../nextjs/pages/pages-router/throw-error.tsx | 7 ---- test/e2e/scenario/nextjsPlugin.scenario.ts | 32 ++++--------------- 5 files changed, 9 insertions(+), 36 deletions(-) delete mode 100644 test/apps/nextjs/pages/pages-router/throw-error.tsx diff --git a/test/apps/nextjs/app/error-test/error.tsx b/test/apps/nextjs/app/error-test/error.tsx index 95e5f41353..072ebbdbbf 100644 --- a/test/apps/nextjs/app/error-test/error.tsx +++ b/test/apps/nextjs/app/error-test/error.tsx @@ -17,7 +17,7 @@ export default function ErrorBoundary({ error, reset }: { error: Error & { diges {error.digest &&

    Digest: {error.digest}

    }
    - Go to Home + Back to Home ) } diff --git a/test/apps/nextjs/app/error-test/page.tsx b/test/apps/nextjs/app/error-test/page.tsx index 70b1a7de53..d3fd22c26f 100644 --- a/test/apps/nextjs/app/error-test/page.tsx +++ b/test/apps/nextjs/app/error-test/page.tsx @@ -14,7 +14,7 @@ export default function ErrorTestPage() { return (
    - ← Back to Home + Back to Home

    Error Test

    ) } diff --git a/test/apps/nextjs/yarn.lock b/test/apps/nextjs/yarn.lock index 4bada3dc99..fca5505def 100644 --- a/test/apps/nextjs/yarn.lock +++ b/test/apps/nextjs/yarn.lock @@ -42,7 +42,7 @@ __metadata: "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz::locator=nextjs%40workspace%3A.": version: 6.31.0 - resolution: "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz#../../../packages/rum-react/package.tgz::hash=ff7bfc&locator=nextjs%40workspace%3A." + resolution: "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz#../../../packages/rum-react/package.tgz::hash=1edc15&locator=nextjs%40workspace%3A." dependencies: "@datadog/browser-core": "npm:6.31.0" "@datadog/browser-rum-core": "npm:6.31.0" @@ -61,7 +61,7 @@ __metadata: optional: true react-router-dom: optional: true - checksum: 10c0/6d2a5f34727c9763abd67aaf9d2ba501e9fd38e8d754a15672e929dc4a37c93c85cdce08ce307122b9d055c688dfee0147315f3679fa377620318b4ab84f00bc + checksum: 10c0/e3ed7ae929dd29836b3f2d7adb910dc9e2fdc6cf6fce81e77e950ed5cd060bb9810c30b8eb8772cf9436203f5c08380b1879454aac856509224bc3a511c9af61 languageName: node linkType: hard diff --git a/test/e2e/scenario/nextjsPlugin.scenario.ts b/test/e2e/scenario/nextjsPlugin.scenario.ts index 88ac86b67b..395e695c71 100644 --- a/test/e2e/scenario/nextjsPlugin.scenario.ts +++ b/test/e2e/scenario/nextjsPlugin.scenario.ts @@ -207,17 +207,16 @@ test.describe('nextjs - errors', () => { .withRum() .withNextjsApp(router) .run(async ({ page, flushEvents, intakeRegistry, withBrowserLogs, browserName }) => { + test.skip( + browserName === 'firefox', + 'firefox is sending the errors in two separate batches, however the last batch is delayed making the test setup to miss it' + ) await page.click('text=Go to Error Test') await page.waitForURL(`**${viewPrefix}/error-test`) await page.click('[data-testid="trigger-error"]') await page.waitForSelector('[data-testid="error-boundary"]') - // TODO: Remove this once we know why Firefox is delaying the error event - if (browserName === 'firefox') { - await page.waitForTimeout(3000) - } - await flushEvents() const customErrors = intakeRegistry.rumErrorEvents.filter((e) => e.error.source === 'custom') From 9254f0195fc00ea810b19d352a2135e7f5e5c9a9 Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Fri, 20 Mar 2026 13:55:07 +0100 Subject: [PATCH 13/13] Prevent duplicate error name --- packages/rum-nextjs/src/domain/error/errorBoundary.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rum-nextjs/src/domain/error/errorBoundary.spec.tsx b/packages/rum-nextjs/src/domain/error/errorBoundary.spec.tsx index 5961e41a4d..78e527fc0b 100644 --- a/packages/rum-nextjs/src/domain/error/errorBoundary.spec.tsx +++ b/packages/rum-nextjs/src/domain/error/errorBoundary.spec.tsx @@ -23,7 +23,7 @@ function initializeNextjsPlugin() { return { addErrorSpy } } -describe('ErrorBoundary', () => { +describe('NextjsErrorBoundary', () => { beforeEach(() => { ignoreConsoleLogs('error', 'Error: error') disableJasmineUncaughtExceptionTracking()