Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0ba2d7c
feat: add addNextjsError, unit and e2e tests, update README.
BeltranBulbarellaDD Mar 6, 2026
a6c24fa
Remove message.includes test assertion from e2e test for firefox.
BeltranBulbarellaDD Mar 6, 2026
aecc13f
Fix addNextjsError context merging
BeltranBulbarellaDD Mar 6, 2026
2327b7d
fix: don't modify next-env.d.ts
BeltranBulbarellaDD Mar 10, 2026
063c84a
Add type guard to addNextjsError
BeltranBulbarellaDD Mar 10, 2026
f9ffc98
Add error boundary and error handling for Next.js
BeltranBulbarellaDD Mar 10, 2026
7fdc006
✅ [RUM-14696] Improve microfrontend e2e test - plugin + module federa…
amortemousque Mar 6, 2026
f804375
👷 Bump staging to staging-11
Mar 9, 2026
a3d2f24
👷 Bump next from 15.3.3 to 15.5.10 (#4292)
dependabot[bot] Mar 9, 2026
b193996
🐛 retry transient 503 errors in telemetry error checking (#4273)
thomas-lebeau Mar 9, 2026
99bb62c
Bump rollup from 4.57.1 to 4.59.0 in /test/apps/react-heavy-spa (#4293)
dependabot[bot] Mar 9, 2026
fbe9a18
✨ [RUM-14736] Add size to ResourceStopOptions (#4296)
BeltranBulbarellaDD Mar 9, 2026
3240a7f
[PROF-13923] Upload source maps to org2 on every deployment (#4282)
thomasbertet Mar 9, 2026
7525462
🐛 [RUM Profiler] Fix long tasks query using wrong clock for duration …
thomasbertet Mar 9, 2026
5d96566
✨ [PANA-6283] Support incremental mutation Change records (#4287)
sethfowler-datadog Mar 10, 2026
3823834
✨ NextJS Pages Router Integration (#4290)
BeltranBulbarellaDD Mar 10, 2026
ad391d4
rename e2e test app
BeltranBulbarellaDD Mar 10, 2026
a9b155f
Add error tests, orginize e2e tests, linter
BeltranBulbarellaDD Mar 10, 2026
9080b6f
Export CI constant
BeltranBulbarellaDD Mar 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ docs/
/developer-extension/.wxt
.env*
!.env.example
.rum-ai-toolkit/

# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
.pnp.*
Expand Down
2 changes: 1 addition & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
variables:
CURRENT_STAGING: staging-10
CURRENT_STAGING: staging-11
APP: 'browser-sdk'
CURRENT_CI_IMAGE: 101
BUILD_STABLE_REGISTRY: 'registry.ddbuild.io'
Expand Down
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ rum-events-format
.yarn
test/**/dist
test/**/.next
test/apps/nextjs-app-router/next-env.d.ts
test/apps/nextjs/next-env.d.ts
yarn.lock
/docs
/developer-extension/.output
Expand Down
1 change: 1 addition & 0 deletions LICENSE-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ prod,react-dom,MIT,Copyright (c) Facebook, Inc. and its affiliates.
dev,typedoc,Apache-2.0,TypeStrong
dev,@eslint/js,MIT,Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
dev,@jsdevtools/coverage-istanbul-loader,MIT,Copyright (c) 2015 James Messinger
dev,@module-federation/enhanced,MIT, Copyright (c) 2020 ScriptedAlchemy LLC (Zack Jackson) Zhou Shaw (zhouxiao)
dev,@playwright/test,Apache-2.0,Copyright Microsoft Corporation
dev,@swc/core,Apache-2.0,Copyright (c) SWC Contributors
dev,@types/chrome,MIT,Copyright Microsoft Corporation
Expand Down
3 changes: 2 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export default tseslint.config(
'test/**/.next',
'test/apps/react-heavy-spa',
'test/apps/react-shopist-like',
'test/apps/nextjs-app-router',
'test/apps/microfrontend',
'test/apps/nextjs',
'sandbox',
'coverage',
'rum-events-format',
Expand Down
11 changes: 10 additions & 1 deletion packages/core/src/tools/stackTrace/handlingStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@ import { computeStackTrace } from './computeStackTrace'
* - No monitored function should encapsulate it, that is why we need to use callMonitored inside it.
*/
export function createHandlingStack(
type: 'console error' | 'action' | 'error' | 'instrumented method' | 'log' | 'react error' | 'view' | 'vital'
type:
| 'console error'
| 'action'
| 'error'
| 'instrumented method'
| 'log'
| 'nextjs error'
| 'react error'
| 'view'
| 'vital'
): string {
/**
* Skip the two internal frames:
Expand Down
6 changes: 4 additions & 2 deletions packages/rum-core/src/boot/rumPublicApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,8 @@ describe('rum public api', () => {
rumPublicApi.stopResource('https://api.example.com/data', {
type: ResourceType.XHR,
statusCode: 200,
context: { responseSize: 1024 },
size: 1024,
context: { requestId: 'abc' },
})

expect(startResourceSpy).toHaveBeenCalledWith(
Expand All @@ -856,7 +857,8 @@ describe('rum public api', () => {
jasmine.objectContaining({
type: ResourceType.XHR,
statusCode: 200,
context: { responseSize: 1024 },
size: 1024,
context: { requestId: 'abc' },
})
)
})
Expand Down
1 change: 1 addition & 0 deletions packages/rum-core/src/boot/rumPublicApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,7 @@ export function makeRumPublicApi(
strategy.stopResource(sanitize(url)!, {
type: sanitize(options && options.type) as ResourceType | undefined,
statusCode: options && options.statusCode,
size: options && options.size,
context: sanitize(options && options.context) as Context,
resourceKey: options && options.resourceKey,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,26 @@ describe('trackManualResources', () => {
})
})

describe('size', () => {
it('should include size when provided at stop', () => {
startResource('https://api.example.com/data')
stopResource('https://api.example.com/data', { size: 1234 })

expect(rawRumEvents).toHaveSize(1)
const resourceEvent = rawRumEvents[0].rawRumEvent as RawRumResourceEvent
expect(resourceEvent.resource.size).toBe(1234)
})

it('should leave size undefined when not provided', () => {
startResource('https://api.example.com/data')
stopResource('https://api.example.com/data')

expect(rawRumEvents).toHaveSize(1)
const resourceEvent = rawRumEvents[0].rawRumEvent as RawRumResourceEvent
expect(resourceEvent.resource.size).toBeUndefined()
})
})

describe('resourceKey', () => {
it('should support resourceKey for tracking same url multiple times', () => {
startResource('https://api.example.com/data', { resourceKey: 'request1' })
Expand Down
64 changes: 30 additions & 34 deletions packages/rum-core/src/domain/resource/trackManualResources.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ClocksState, Context, ResourceType } from '@datadog/browser-core'
import type { Context, ResourceType } from '@datadog/browser-core'
import { clocksNow, elapsed, ResourceType as ResourceTypeEnum, toServerDuration } from '@datadog/browser-core'
import type { RawRumResourceEvent } from '../../rawRumEvent.types'
import { RumEventType } from '../../rawRumEvent.types'
Expand Down Expand Up @@ -42,6 +42,11 @@ export interface ResourceStopOptions {
*/
statusCode?: number

/**
* Resource size in bytes
*/
size?: number

/**
* Resource context
*/
Expand All @@ -61,38 +66,6 @@ export interface ManualResourceData {
}

export function trackManualResources(lifeCycle: LifeCycle, resourceTracker: EventTracker<ManualResourceData>) {
function emitResource(
id: string,
startClocks: ClocksState,
data: ManualResourceData,
statusCode?: number,
endClocks?: ClocksState
) {
const duration = endClocks ? elapsed(startClocks.relative, endClocks.relative) : undefined

const rawRumEvent: RawRumResourceEvent = {
date: startClocks.timeStamp,
type: RumEventType.RESOURCE,
resource: {
id,
type: data.type || ResourceTypeEnum.OTHER,
url: sanitizeIfLongDataUrl(data.url),
duration: duration !== undefined ? toServerDuration(duration) : undefined,
method: data.method,
status_code: statusCode,
},
_dd: {},
context: data.context,
}

lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, {
rawRumEvent,
startClocks,
duration,
domainContext: { isManual: true as const },
})
}

function startManualResource(url: string, options: ResourceOptions = {}, startClocks = clocksNow()) {
const lookupKey = options.resourceKey ?? url

Expand All @@ -114,7 +87,30 @@ export function trackManualResources(lifeCycle: LifeCycle, resourceTracker: Even
return
}

emitResource(stopped.id, stopped.startClocks, stopped, options.statusCode, stopClocks)
const duration = elapsed(stopped.startClocks.relative, stopClocks.relative)

const rawRumEvent: RawRumResourceEvent = {
date: stopped.startClocks.timeStamp,
type: RumEventType.RESOURCE,
resource: {
id: stopped.id,
type: stopped.type || ResourceTypeEnum.OTHER,
url: sanitizeIfLongDataUrl(stopped.url),
duration: toServerDuration(duration),
method: stopped.method,
status_code: options.statusCode,
size: options.size,
},
_dd: {},
context: stopped.context,
}

lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, {
rawRumEvent,
startClocks: stopped.startClocks,
duration,
domainContext: { isManual: true },
})
}

return {
Expand Down
93 changes: 90 additions & 3 deletions packages/rum-nextjs/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# RUM Browser Monitoring - NEXTJS integration

This package provides NextJS App Router integration for Datadog Browser RUM.
This package provides NextJS integration for Datadog Browser RUM, supporting both the App Router and Pages Router.

Requires Next.js v15.3+, which supports the [`instrumentation-client`][1] file convention.
Both routers require **Next.js v15.3+**, which supports the [`instrumentation-client`][1] file convention.

[1]: https://nextjs.org/docs/app/api-reference/file-conventions/instrumentation-client

# Usage
# App Router Usage

## 1. Create an `instrumentation-client.js` file in the root of your Next.js project

Expand Down Expand Up @@ -43,3 +43,90 @@ export default function RootLayout({ children }: { children: React.ReactNode })
)
}
```

## 3. Report errors from error boundaries

Next.js uses [error boundaries](https://nextjs.org/docs/app/api-reference/file-conventions/error) (`error.tsx` files) to catch uncaught exceptions in each route segment. Use `addNextjsError` inside these boundaries to report errors to Datadog RUM.

For **Server Component** errors, Next.js sends a generic message to the client and attaches `error.digest` — a hash that links the client-side error to your server-side logs. For **Client Component** errors, `error.message` is the original message and `digest` is absent.

```tsx
// app/error.tsx (or app/dashboard/error.tsx, etc.)
'use client'

import { useEffect } from 'react'
import { addNextjsError } from '@datadog/browser-rum-nextjs'

export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
useEffect(() => {
addNextjsError(error)
}, [error])

return <button onClick={reset}>Try again</button>
}
```

For errors in the **root layout**, use `global-error.tsx` — it must provide its own `<html>` and `<body>` tags since the root layout is replaced:

```tsx
// app/global-error.tsx
'use client'

import { useEffect } from 'react'
import { addNextjsError } from '@datadog/browser-rum-nextjs'

export default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
useEffect(() => {
addNextjsError(error)
}, [error])

return (
<html>
<body>
<button onClick={reset}>Try again</button>
</body>
</html>
)
}
```

You can also pass custom context:

```ts
addNextjsError(error, { route: '/dashboard', userId: '123' })
```

# Pages Router Usage

## 1. Create an `instrumentation-client.js` file in the root of your Next.js project

Initialize the Datadog RUM SDK with the `nextjsPlugin`. The `onRouterTransitionStart` export is **not needed** for Pages Router.

```js
import { datadogRum } from '@datadog/browser-rum'
import { nextjsPlugin } from '@datadog/browser-rum-nextjs'

datadogRum.init({
applicationId: '<APP_ID>',
clientToken: '<CLIENT_TOKEN>',
site: 'datadoghq.com',
plugins: [nextjsPlugin()],
})
```

## 2. Call the DatadogPagesRouter component from your custom App.

```tsx
// pages/_app.tsx
import type { AppProps } from 'next/app'
import { DatadogPagesRouter } from '@datadog/browser-rum-nextjs'

export default function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<DatadogPagesRouter />
<Component {...pageProps} />
</>
)
}
```
2 changes: 1 addition & 1 deletion packages/rum-nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
},
"devDependencies": {
"@types/react": "19.2.11",
"next": "15.3.3",
"next": "15.5.10",
"react": "19.2.4"
}
}
Loading
Loading