Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ rum-events-format
test/**/dist
test/**/.next
test/apps/nextjs-app-router/next-env.d.ts
test/apps/nextjs-pages-router/next-env.d.ts
yarn.lock
/docs
/developer-extension/.output
Expand Down
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default tseslint.config(
'test/apps/react-heavy-spa',
'test/apps/react-shopist-like',
'test/apps/nextjs-app-router',
'test/apps/nextjs-pages-router',
'sandbox',
'coverage',
'rum-events-format',
Expand Down
39 changes: 37 additions & 2 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.

[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,38 @@ export default function RootLayout({ children }: { children: React.ReactNode })
)
}
```

# 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} />
</>
)
}
```
20 changes: 20 additions & 0 deletions packages/rum-nextjs/src/domain/datadogPagesRouter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client'
Comment thread
BeltranBulbarellaDD marked this conversation as resolved.
Outdated

import { useRef } from 'react'
import { useRouter } from 'next/router'
import { startNextjsView } from './nextjsPlugin'

export function DatadogPagesRouter() {
const router = useRouter()
const previousAsPath = useRef<string | null>(null)

if (previousAsPath.current !== router.asPath) {
Comment thread
BeltranBulbarellaDD marked this conversation as resolved.
Outdated
previousAsPath.current = router.asPath
// router.pathname is the route pattern (e.g., "/user/[id]") — used as the view name
// router.asPath is the actual URL (e.g., "/user/42") — used to detect navigations between
// different concrete URLs of the same dynamic route (e.g., /user/42 → /user/43)
startNextjsView(router.pathname)
}

return null
}
1 change: 1 addition & 0 deletions packages/rum-nextjs/src/entries/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { nextjsPlugin, onRouterTransitionStart } from '../domain/nextjsPlugin'
export type { NextjsPlugin } from '../domain/nextjsPlugin'
export { DatadogAppRouter } from '../domain/datadogAppRouter'
export { DatadogPagesRouter } from '../domain/datadogPagesRouter'
1 change: 1 addition & 0 deletions scripts/build/build-test-apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ runMain(async () => {
await buildReactRouterv7App()
await buildExtensions()
buildApp('test/apps/nextjs-app-router')
buildApp('test/apps/nextjs-pages-router')

printLog('Test apps and extensions built successfully.')
})
Expand Down
2 changes: 1 addition & 1 deletion test/apps/nextjs-app-router/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
12 changes: 6 additions & 6 deletions test/apps/nextjs-app-router/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ __metadata:

"@datadog/browser-core@file:../../../packages/core/package.tgz::locator=nextjs-app-router%40workspace%3A.":
version: 6.30.1
resolution: "@datadog/browser-core@file:../../../packages/core/package.tgz#../../../packages/core/package.tgz::hash=e62f42&locator=nextjs-app-router%40workspace%3A."
checksum: 10c0/3569d76a2863c745ad9804953972f7ca3cc236a8239b53cb095c44e54290fde89bf880776a4baad096306744ffcee9bba28beddac4b0593e0c9f3a5d19998488
resolution: "@datadog/browser-core@file:../../../packages/core/package.tgz#../../../packages/core/package.tgz::hash=c45ebb&locator=nextjs-app-router%40workspace%3A."
checksum: 10c0/19a0212b06a2d6d1bf74a47c3c3be3e1bf8ce6c78c403e6968a317d4e5da95901d78297beafa025fa2e97376bfc0a81ad23c9c677994b2912139fdafc6aead44
languageName: node
linkType: hard

Expand All @@ -23,7 +23,7 @@ __metadata:

"@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz::locator=nextjs-app-router%40workspace%3A.":
version: 6.30.1
resolution: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz#../../../packages/rum-nextjs/package.tgz::hash=2c2ac0&locator=nextjs-app-router%40workspace%3A."
resolution: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz#../../../packages/rum-nextjs/package.tgz::hash=710fdb&locator=nextjs-app-router%40workspace%3A."
dependencies:
"@datadog/browser-core": "npm:6.30.1"
"@datadog/browser-rum-core": "npm:6.30.1"
Expand All @@ -35,7 +35,7 @@ __metadata:
optional: true
react:
optional: true
checksum: 10c0/b72cc517a492ee6082eefb2a665201bee5c3f38a8436f6a6e5882bdc49923985ef0a3873e0611390c695669309589af8074bde7c50582ccc4e4c391a7a95c079
checksum: 10c0/393eee84c705a43aaad0d6be282815da5d2c3953a812aee5e8b9deb36d9d929bd95f650b027318b44af6ad04e7632fcf63980a00fd80d5bb5f7cbbd04ab2cc68
languageName: node
linkType: hard

Expand Down Expand Up @@ -66,7 +66,7 @@ __metadata:

"@datadog/browser-rum@file:../../../packages/rum/package.tgz::locator=nextjs-app-router%40workspace%3A.":
version: 6.30.1
resolution: "@datadog/browser-rum@file:../../../packages/rum/package.tgz#../../../packages/rum/package.tgz::hash=03a1d8&locator=nextjs-app-router%40workspace%3A."
resolution: "@datadog/browser-rum@file:../../../packages/rum/package.tgz#../../../packages/rum/package.tgz::hash=9cfd3b&locator=nextjs-app-router%40workspace%3A."
dependencies:
"@datadog/browser-core": "npm:6.30.1"
"@datadog/browser-rum-core": "npm:6.30.1"
Expand All @@ -75,7 +75,7 @@ __metadata:
peerDependenciesMeta:
"@datadog/browser-logs":
optional: true
checksum: 10c0/4b82022e8aec51284cd4c7436999e63a5f944fed37330dd13b27827fe9db1acb1ca1491cfc45454ebef089145d902bc86c3202984709c8f369383fc340c33790
checksum: 10c0/95237d8e152fffc25e395f8b417eff467bd90c367ab9017d7bab90eb77f626dcc75820e7163354e154175c82c091318cbf3dd5ea5bbd5a0d903660c2f939fb4e
languageName: node
linkType: hard

Expand Down
3 changes: 3 additions & 0 deletions test/apps/nextjs-pages-router/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.next
node_modules
.yarn/*
13 changes: 13 additions & 0 deletions test/apps/nextjs-pages-router/instrumentation-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { datadogRum } from '@datadog/browser-rum'
import { nextjsPlugin } from '@datadog/browser-rum-nextjs'

const params = new URLSearchParams(window.location.search)
const config = params.get('rum-config')
const context = params.get('rum-context')

if (config && !datadogRum.getInitConfiguration()) {
datadogRum.init({ ...JSON.parse(config), plugins: [nextjsPlugin()] })
if (context) {
datadogRum.setGlobalContext(JSON.parse(context))
}
}
6 changes: 6 additions & 0 deletions test/apps/nextjs-pages-router/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
8 changes: 8 additions & 0 deletions test/apps/nextjs-pages-router/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
turbopack: {
root: __dirname,
},
}

module.exports = nextConfig
33 changes: 33 additions & 0 deletions test/apps/nextjs-pages-router/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "nextjs-pages-router",
"private": true,
"scripts": {
"dev": "next dev -p 0",
"build": "next build",
"start": "next start -p 0"
},
"dependencies": {
"@datadog/browser-rum": "file:../../../packages/rum/package.tgz",
"@datadog/browser-rum-nextjs": "file:../../../packages/rum-nextjs/package.tgz",
"next": "16.1.6",
"react": "19.2.3",
"react-dom": "19.2.3"
},
"resolutions": {
"@datadog/browser-rum-core": "file:../../../packages/rum-core/package.tgz",
"@datadog/browser-core": "file:../../../packages/core/package.tgz",
"@datadog/browser-rum": "file:../../../packages/rum/package.tgz",
"@datadog/browser-rum-nextjs": "file:../../../packages/rum-nextjs/package.tgz",
"@datadog/browser-rum-slim": "file:../../../packages/rum-slim/package.tgz",
"@datadog/browser-worker": "file:../../../packages/worker/package.tgz"
},
"devDependencies": {
"@types/node": "22.16.0",
"@types/react": "19.2.8",
"@types/react-dom": "19.2.3",
"typescript": "5.9.3"
},
"volta": {
"extends": "../../../package.json"
}
}
19 changes: 19 additions & 0 deletions test/apps/nextjs-pages-router/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { AppProps } from 'next/app'
import Link from 'next/link'
import { DatadogPagesRouter } from '@datadog/browser-rum-nextjs'

export default function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<DatadogPagesRouter />
<nav style={{ background: '#632ca6', padding: '1rem', marginBottom: '1rem' }}>
<Link href="/" style={{ color: 'white', textDecoration: 'none' }}>
Home
</Link>
</nav>
<main style={{ maxWidth: '800px', margin: '0 auto', padding: '1rem' }}>
<Component {...pageProps} />
</main>
</>
)
}
16 changes: 16 additions & 0 deletions test/apps/nextjs-pages-router/pages/guides/[...slug].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Link from 'next/link'
import { useRouter } from 'next/router'

export default function GuidesPage() {
const router = useRouter()
const { slug } = router.query
const slugParts = Array.isArray(slug) ? slug : slug ? [slug] : []

return (
<div>
<Link href="/">← Back to Home</Link>
<h1>Guides: {slugParts.join('/')}</h1>
<p>This is a catch-all route testing slug normalization.</p>
</div>
)
}
17 changes: 17 additions & 0 deletions test/apps/nextjs-pages-router/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Link from 'next/link'

export default function HomePage() {
return (
<div>
<h1>Home</h1>
<ul>
<li>
<Link href="/user/42?admin=true">Go to User 42</Link>
</li>
<li>
<Link href="/guides/123">Go to Guides 123</Link>
</li>
</ul>
</div>
)
}
16 changes: 16 additions & 0 deletions test/apps/nextjs-pages-router/pages/user/[id].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Link from 'next/link'
import { useRouter } from 'next/router'

export default function UserPage() {
const router = useRouter()
const { id } = router.query

return (
<div>
<Link href="/">← Back to Home</Link>
<h1>User {id}</h1>
<p>This is a dynamic route testing view name normalization.</p>
<Link href="/user/999?admin=true">Go to User 999</Link>
</div>
)
}
Binary file added test/apps/nextjs-pages-router/public/favicon.ico
Binary file not shown.
21 changes: 21 additions & 0 deletions test/apps/nextjs-pages-router/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": { "@/*": ["./*"] },
"target": "ES2017"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],
"exclude": ["node_modules"]
}
Loading