diff --git a/.gitignore b/.gitignore index cc7c5d0379..155dcb1867 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ playwright-report/ # Claude Code local files *.local.md .claude/settings.local.json + +# Rum AI Toolkit +.rum-ai-toolkit/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index cd4dd6cc00..3d230f1954 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,6 +5,7 @@ coverage rum-events-format .yarn test/**/dist +test/**/.next yarn.lock /docs /developer-extension/.output diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index a3ac2d3aa6..cc4f97c3a9 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -57,6 +57,7 @@ dev,karma-spec-reporter,MIT,Copyright 2015 Michael Lex dev,karma-webpack,MIT,Copyright JS Foundation and other contributors dev,lerna,MIT,Copyright 2015-present Lerna Contributors dev,minimatch,ISC,Copyright (c) Isaac Z. Schlueter and Contributors +dev,next,MIT,Copyright (c) 2025 Vercel, Inc. dev,node-forge,BSD,Copyright (c) 2010, Digital Bazaar, Inc. dev,pako,MIT,(C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin dev,prettier,MIT,Copyright James Long and contributors diff --git a/eslint.config.mjs b/eslint.config.mjs index fe6928b3dd..8aeee974fd 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -25,6 +25,9 @@ export default tseslint.config( 'packages/*/cjs', 'packages/*/esm', 'test/**/dist', + 'test/**/.next', + 'test/apps/nextjs-app-router', + 'test/apps/nextjs-pages-router', 'test/apps/react-heavy-spa', 'test/apps/react-shopist-like', 'sandbox', diff --git a/packages/core/src/tools/instrumentMethod.spec.ts b/packages/core/src/tools/instrumentMethod.spec.ts index 097f665bc4..75b309d442 100644 --- a/packages/core/src/tools/instrumentMethod.spec.ts +++ b/packages/core/src/tools/instrumentMethod.spec.ts @@ -266,7 +266,7 @@ describe('instrumentSetter', () => { it('does not use the Zone.js setTimeout function', () => { const zoneJsSetTimeoutSpy = jasmine.createSpy() - zoneJs.replaceProperty(window, 'setTimeout', zoneJsSetTimeoutSpy) + zoneJs.replaceProperty(window, 'setTimeout', zoneJsSetTimeoutSpy as unknown as typeof window.setTimeout) const object = {} as { foo: number } Object.defineProperty(object, 'foo', { set: noop, configurable: true }) diff --git a/packages/rum-core/src/domain/tracing/identifier.spec.ts b/packages/rum-core/src/domain/tracing/identifier.spec.ts index 6835e2cbc3..7194422728 100644 --- a/packages/rum-core/src/domain/tracing/identifier.spec.ts +++ b/packages/rum-core/src/domain/tracing/identifier.spec.ts @@ -38,7 +38,7 @@ describe('toPaddedHexadecimalString', () => { }) function mockRandomValues(cb: (buffer: Uint8Array) => void) { - spyOn(window.crypto, 'getRandomValues').and.callFake((bufferView) => { + spyOn(window.crypto, 'getRandomValues').and.callFake((bufferView: ArrayBufferView) => { cb(new Uint8Array(bufferView.buffer)) return bufferView }) diff --git a/packages/rum-nextjs/app-router/package.json b/packages/rum-nextjs/app-router/package.json new file mode 100644 index 0000000000..487a637f91 --- /dev/null +++ b/packages/rum-nextjs/app-router/package.json @@ -0,0 +1,7 @@ +{ + "name": "@datadog/browser-rum-nextjs/app-router", + "private": true, + "main": "../cjs/entries/appRouter.js", + "module": "../esm/entries/appRouter.js", + "types": "../cjs/entries/appRouter.d.ts" +} diff --git a/packages/rum-nextjs/package.json b/packages/rum-nextjs/package.json new file mode 100644 index 0000000000..fd0588f82c --- /dev/null +++ b/packages/rum-nextjs/package.json @@ -0,0 +1,43 @@ +{ + "name": "@datadog/browser-rum-nextjs", + "license": "Apache-2.0", + "main": "cjs/entries/main.js", + "module": "esm/entries/main.js", + "types": "cjs/entries/main.d.ts", + "scripts": { + "build": "node ../../scripts/build/build-package.ts --modules", + "prepack": "npm run build" + }, + "dependencies": { + "@datadog/browser-core": "6.27.1", + "@datadog/browser-rum-core": "6.27.1" + }, + "peerDependencies": { + "next": ">=12.0.0", + "react": ">=18.0.0" + }, + "peerDependenciesMeta": { + "next": { + "optional": true + }, + "react": { + "optional": true + } + }, + "repository": { + "type": "git", + "url": "https://github.com/DataDog/browser-sdk.git", + "directory": "packages/rum-nextjs" + }, + "volta": { + "extends": "../../package.json" + }, + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@types/react": "19.2.11", + "next": "15.3.3", + "react": "19.2.4" + } +} diff --git a/packages/rum-nextjs/pages-router/package.json b/packages/rum-nextjs/pages-router/package.json new file mode 100644 index 0000000000..97b1022e48 --- /dev/null +++ b/packages/rum-nextjs/pages-router/package.json @@ -0,0 +1,7 @@ +{ + "name": "@datadog/browser-rum-nextjs/pages-router", + "private": true, + "main": "../cjs/entries/pagesRouter.js", + "module": "../esm/entries/pagesRouter.js", + "types": "../cjs/entries/pagesRouter.d.ts" +} diff --git a/packages/rum-nextjs/src/domain/addDurationVital.spec.ts b/packages/rum-nextjs/src/domain/addDurationVital.spec.ts new file mode 100644 index 0000000000..00de345e19 --- /dev/null +++ b/packages/rum-nextjs/src/domain/addDurationVital.spec.ts @@ -0,0 +1,52 @@ +import type { RumPublicApi, RumInitConfiguration } from '@datadog/browser-rum-core' +import { registerCleanupTask } from '../../../core/test' +import { nextjsPlugin, resetNextjsPlugin } from './nextjsPlugin' +import { addDurationVital } from './addDurationVital' + +const routerTypes = ['app', 'pages'] as const + +routerTypes.forEach((routerType) => { + describe(`addDurationVital (router: '${routerType}')`, () => { + beforeEach(() => { + registerCleanupTask(() => { + resetNextjsPlugin() + }) + }) + + it('should forward to rumPublicApi.addDurationVital after nextjsPlugin init', () => { + const addDurationVitalSpy = jasmine.createSpy() + const publicApi = { addDurationVital: addDurationVitalSpy } as unknown as RumPublicApi + + addDurationVital('reactComponentRender', { + description: 'MyComponent', + startTime: 0 as any, + duration: 100, + }) + + expect(addDurationVitalSpy).not.toHaveBeenCalled() + + const plugin = nextjsPlugin({ router: routerType }) + plugin.onInit({ publicApi, initConfiguration: {} as RumInitConfiguration }) + + expect(addDurationVitalSpy).toHaveBeenCalledTimes(1) + expect(addDurationVitalSpy.calls.mostRecent().args[0]).toBe('reactComponentRender') + expect(addDurationVitalSpy.calls.mostRecent().args[1].description).toBe('MyComponent') + }) + + it('should call immediately if nextjsPlugin is already initialized', () => { + const addDurationVitalSpy = jasmine.createSpy() + const publicApi = { addDurationVital: addDurationVitalSpy } as unknown as RumPublicApi + + const plugin = nextjsPlugin({ router: routerType }) + plugin.onInit({ publicApi, initConfiguration: {} as RumInitConfiguration }) + + addDurationVital('reactComponentRender', { + description: 'MyComponent', + startTime: 0 as any, + duration: 50, + }) + + expect(addDurationVitalSpy).toHaveBeenCalledTimes(1) + }) + }) +}) diff --git a/packages/rum-nextjs/src/domain/addDurationVital.ts b/packages/rum-nextjs/src/domain/addDurationVital.ts new file mode 100644 index 0000000000..46a099efbe --- /dev/null +++ b/packages/rum-nextjs/src/domain/addDurationVital.ts @@ -0,0 +1,8 @@ +import type { RumPublicApi } from '@datadog/browser-rum-core' +import { onRumInit } from './nextjsPlugin' + +export const addDurationVital: RumPublicApi['addDurationVital'] = (name, options) => { + onRumInit((_, rumPublicApi) => { + rumPublicApi.addDurationVital(name, options) + }) +} diff --git a/packages/rum-nextjs/src/domain/appRouter/datadogRumProvider.tsx b/packages/rum-nextjs/src/domain/appRouter/datadogRumProvider.tsx new file mode 100644 index 0000000000..9a44628232 --- /dev/null +++ b/packages/rum-nextjs/src/domain/appRouter/datadogRumProvider.tsx @@ -0,0 +1,58 @@ +'use client' + +import React, { useRef, useEffect } from 'react' +import { usePathname, useParams } from 'next/navigation' +import { computeViewNameFromParams } from '../computeViewNameFromParams' +import { startNextjsView } from '../startNextjsView' + +/** + * Tracks Next.js App Router views via `usePathname` and `useParams`. + * + * @example + * ```tsx + * // app/components/datadog-rum-provider.tsx + * 'use client' + * + * import { datadogRum } from '@datadog/browser-rum' + * import { nextjsPlugin } from '@datadog/browser-rum-nextjs' + * import { DatadogRumProvider } from '@datadog/browser-rum-nextjs/app-router' + * + * datadogRum.init({ + * applicationId: '', + * clientToken: '', + * plugins: [nextjsPlugin({ router: 'app' })], + * }) + * + * export default DatadogRumProvider + * ``` + * + * ```tsx + * // app/layout.tsx + * import DatadogRumProvider from './components/datadog-rum-provider' + * + * export default function RootLayout({ children }: { children: React.ReactNode }) { + * return ( + * + * + * {children} + * + * + * ) + * } + * ``` + */ +export function DatadogRumProvider({ children }: { children: React.ReactNode }) { + const pathname = usePathname() + const params = useParams() + const previousPathnameRef = useRef(null) + + useEffect(() => { + if (previousPathnameRef.current !== pathname) { + previousPathnameRef.current = pathname + const viewName = computeViewNameFromParams(pathname, params as Record) + startNextjsView(viewName) + } + }, [pathname, params]) + + return <>{children} +} diff --git a/packages/rum-nextjs/src/domain/computeViewNameFromParams.spec.ts b/packages/rum-nextjs/src/domain/computeViewNameFromParams.spec.ts new file mode 100644 index 0000000000..e20f748dad --- /dev/null +++ b/packages/rum-nextjs/src/domain/computeViewNameFromParams.spec.ts @@ -0,0 +1,29 @@ +import { computeViewNameFromParams } from './computeViewNameFromParams' + +describe('computeViewNameFromParams', () => { + // prettier-ignore + const cases: Array<[string, string, Record, string]> = [ + // [description, pathname, params, expected] + // Static routes + ['static path', '/about', {}, '/about'], + ['nested static path', '/static/page', {}, '/static/page'], + // Single dynamic segment + ['single dynamic segment', '/users/123', { id: '123' }, '/users/[id]'], + // Multiple dynamic segments + ['multiple dynamic segments', '/users/123/posts/456', { userId: '123', postId: '456' }, '/users/[userId]/posts/[postId]'], + // Catch-all routes + ['catch-all with multiple segments', '/docs/a/b/c', { slug: ['a', 'b', 'c'] }, '/docs/[...slug]'], + ['catch-all with single segment', '/docs/intro', { slug: ['intro'] }, '/docs/[...slug]'], + // Ordering + ['longer values replaced first', '/items/123/1', { id: '123', subId: '1' }, '/items/[id]/[subId]'], + // Edge cases + ['undefined param values ignored', '/users/123', { id: '123', optional: undefined }, '/users/[id]'], + ['empty string param values ignored', '/users/123', { id: '123', empty: '' }, '/users/[id]'], + ['empty catch-all array ignored', '/docs', { slug: [] }, '/docs'], + ] + cases.forEach(([description, pathname, params, expected]) => { + it(`${description}: "${pathname}" → "${expected}"`, () => { + expect(computeViewNameFromParams(pathname, params)).toBe(expected) + }) + }) +}) diff --git a/packages/rum-nextjs/src/domain/computeViewNameFromParams.ts b/packages/rum-nextjs/src/domain/computeViewNameFromParams.ts new file mode 100644 index 0000000000..1e7b5ac89f --- /dev/null +++ b/packages/rum-nextjs/src/domain/computeViewNameFromParams.ts @@ -0,0 +1,35 @@ +export function computeViewNameFromParams( + pathname: string, + params: Record +): string { + if (!params || Object.keys(params).length === 0) { + return pathname + } + + let viewName = pathname + + // Sort params by value length descending to replace longer values first. + // Prevents partial replacements (e.g., replacing '1' before '123'). + const sortedParams = Object.entries(params).sort((a, b) => { + const aLen = Array.isArray(a[1]) ? a[1].join('/').length : (a[1]?.length ?? 0) + const bLen = Array.isArray(b[1]) ? b[1].join('/').length : (b[1]?.length ?? 0) + return bLen - aLen + }) + + for (const [paramName, paramValue] of sortedParams) { + if (paramValue === undefined) { + continue + } + + if (Array.isArray(paramValue)) { + const joinedValue = paramValue.join('/') + if (joinedValue && viewName.includes(joinedValue)) { + viewName = viewName.replace(joinedValue, `[...${paramName}]`) + } + } else if (paramValue) { + viewName = viewName.replace(paramValue, `[${paramName}]`) + } + } + + return viewName +} diff --git a/packages/rum-nextjs/src/domain/error/reportNextjsError.spec.ts b/packages/rum-nextjs/src/domain/error/reportNextjsError.spec.ts new file mode 100644 index 0000000000..10ac629a8c --- /dev/null +++ b/packages/rum-nextjs/src/domain/error/reportNextjsError.spec.ts @@ -0,0 +1,134 @@ +import type { RelativeTime, TimeStamp } from '@datadog/browser-core' +import { clocksNow, generateUUID, noop } from '@datadog/browser-core' +import type { RumInitConfiguration, RumPublicApi } from '@datadog/browser-rum-core' +import { RumEventType } from '@datadog/browser-rum-core' +import { registerCleanupTask, replaceMockable } from '../../../../core/test' +import { initializeNextjsPlugin } from '../../../test/initializeNextjsPlugin' +import { nextjsPlugin, resetNextjsPlugin } from '../nextjsPlugin' +import { reportNextjsError } from './reportNextjsError' + +const FAKE_RELATIVE_TIME = 100 as RelativeTime +const FAKE_TIMESTAMP = 1000 as TimeStamp +const FAKE_UUID = 'fake-uuid-1234' + +describe('reportNextjsError', () => { + beforeEach(() => { + replaceMockable(clocksNow, () => ({ relative: FAKE_RELATIVE_TIME, timeStamp: FAKE_TIMESTAMP })) + replaceMockable(generateUUID, () => FAKE_UUID) + }) + + it('reports an App Router error with digest', () => { + const addEventSpy = jasmine.createSpy() + initializeNextjsPlugin({ addEvent: addEventSpy }) + + const error = new Error('Test error') + ;(error as any).digest = 'abc123' + + reportNextjsError(error, noop) + + expect(addEventSpy).toHaveBeenCalledOnceWith( + FAKE_RELATIVE_TIME, + jasmine.objectContaining({ + type: RumEventType.ERROR, + date: FAKE_TIMESTAMP, + error: jasmine.objectContaining({ + id: FAKE_UUID, + message: 'Test error', + source: 'source', + type: 'Error', + handling: 'unhandled', + source_type: 'browser', + }), + context: { + framework: 'nextjs', + router: 'app', + digest: 'abc123', + }, + }), + { error } + ) + }) + + it('reports a Pages Router error with statusCode', () => { + const addEventSpy = jasmine.createSpy() + initializeNextjsPlugin({ + configuration: { router: 'pages' }, + addEvent: addEventSpy, + }) + + const error = new Error('Server error') + + reportNextjsError(error, 500) + + expect(addEventSpy).toHaveBeenCalledOnceWith( + FAKE_RELATIVE_TIME, + jasmine.objectContaining({ + context: { + framework: 'nextjs', + router: 'pages', + statusCode: 500, + }, + }), + { error } + ) + }) + + it('detects App Router when second argument is a function', () => { + const addEventSpy = jasmine.createSpy() + initializeNextjsPlugin({ addEvent: addEventSpy }) + + reportNextjsError(new Error('test'), noop) + + const event = addEventSpy.calls.mostRecent().args[1] + expect(event.context.router).toBe('app') + }) + + it('detects Pages Router when second argument is a number', () => { + const addEventSpy = jasmine.createSpy() + initializeNextjsPlugin({ addEvent: addEventSpy }) + + reportNextjsError(new Error('test'), 404) + + const event = addEventSpy.calls.mostRecent().args[1] + expect(event.context.router).toBe('pages') + }) + + it('defaults to Pages Router when no second argument', () => { + const addEventSpy = jasmine.createSpy() + initializeNextjsPlugin({ addEvent: addEventSpy }) + + reportNextjsError(new Error('test')) + + const event = addEventSpy.calls.mostRecent().args[1] + expect(event.context.router).toBe('pages') + }) + + it('does not include digest when not present on error', () => { + const addEventSpy = jasmine.createSpy() + initializeNextjsPlugin({ addEvent: addEventSpy }) + + reportNextjsError(new Error('test'), noop) + + const event = addEventSpy.calls.mostRecent().args[1] + expect(event.context.digest).toBeUndefined() + }) + + it('queues the error if RUM has not started yet', () => { + const addEventSpy = jasmine.createSpy() + + reportNextjsError(new Error('queued error'), noop) + + expect(addEventSpy).not.toHaveBeenCalled() + + const plugin = nextjsPlugin({ router: 'app' }) + plugin.onInit({ + publicApi: {} as RumPublicApi, + initConfiguration: {} as RumInitConfiguration, + }) + plugin.onRumStart({ addEvent: addEventSpy }) + + registerCleanupTask(() => resetNextjsPlugin()) + + expect(addEventSpy).toHaveBeenCalled() + }) +}) diff --git a/packages/rum-nextjs/src/domain/error/reportNextjsError.ts b/packages/rum-nextjs/src/domain/error/reportNextjsError.ts new file mode 100644 index 0000000000..0b41f20443 --- /dev/null +++ b/packages/rum-nextjs/src/domain/error/reportNextjsError.ts @@ -0,0 +1,53 @@ +import { callMonitored, clocksNow, ErrorHandling, ErrorSource, generateUUID, mockable } from '@datadog/browser-core' +import { RumEventType } from '@datadog/browser-rum-core' +import { onRumStart } from '../nextjsPlugin' + +interface NextjsErrorContext { + digest?: string + statusCode?: number + router: 'app' | 'pages' +} + +export function reportNextjsError(error: Error & { digest?: string }, resetOrStatusCode?: (() => void) | number) { + const startClocks = mockable(clocksNow)() + + const context: NextjsErrorContext = { + router: typeof resetOrStatusCode === 'function' ? 'app' : 'pages', + } + + if (error.digest) { + context.digest = error.digest + } + + if (typeof resetOrStatusCode === 'number') { + context.statusCode = resetOrStatusCode + } + + onRumStart((addEvent) => { + callMonitored(() => { + addEvent( + startClocks.relative, + { + type: RumEventType.ERROR, + date: startClocks.timeStamp, + error: { + id: mockable(generateUUID)(), + message: error.message, + source: ErrorSource.SOURCE, + stack: error.stack, + type: error.name, + handling: ErrorHandling.UNHANDLED, + source_type: 'browser', + }, + context: { + framework: 'nextjs', + ...context, + }, + }, + { + error, + } + ) + }) + }) +} diff --git a/packages/rum-nextjs/src/domain/nextjsPlugin.spec.ts b/packages/rum-nextjs/src/domain/nextjsPlugin.spec.ts new file mode 100644 index 0000000000..38c1c57632 --- /dev/null +++ b/packages/rum-nextjs/src/domain/nextjsPlugin.spec.ts @@ -0,0 +1,95 @@ +import type { RumInitConfiguration, RumPublicApi } from '@datadog/browser-rum-core' +import { registerCleanupTask } from '../../../core/test' +import { nextjsPlugin, onRumInit, onRumStart, resetNextjsPlugin } from './nextjsPlugin' + +const PUBLIC_API = {} as RumPublicApi +const INIT_CONFIGURATION = {} as RumInitConfiguration + +const routerTypes = ['app', 'pages'] as const + +routerTypes.forEach((routerType) => { + describe(`nextjsPlugin (router: '${routerType}')`, () => { + beforeEach(() => { + registerCleanupTask(() => { + resetNextjsPlugin() + }) + }) + + it('returns a plugin object', () => { + const plugin = nextjsPlugin({ router: routerType }) + + expect(plugin).toEqual( + jasmine.objectContaining({ + name: 'nextjs', + onInit: jasmine.any(Function), + onRumStart: jasmine.any(Function), + }) + ) + }) + + it('sets trackViewsManually to true', () => { + const initConfiguration = { ...INIT_CONFIGURATION } + + nextjsPlugin({ router: routerType }).onInit({ publicApi: PUBLIC_API, initConfiguration }) + + expect(initConfiguration.trackViewsManually).toBe(true) + }) + + it('calls onRumInit subscribers during onInit', () => { + const callbackSpy = jasmine.createSpy() + const pluginConfiguration = { router: routerType } + onRumInit(callbackSpy) + + expect(callbackSpy).not.toHaveBeenCalled() + + nextjsPlugin(pluginConfiguration).onInit({ + publicApi: PUBLIC_API, + initConfiguration: INIT_CONFIGURATION, + }) + + expect(callbackSpy).toHaveBeenCalledTimes(1) + expect(callbackSpy.calls.mostRecent().args[0]).toBe(pluginConfiguration) + expect(callbackSpy.calls.mostRecent().args[1]).toBe(PUBLIC_API) + }) + + it('calls onRumInit subscriber immediately if already initialized', () => { + const callbackSpy = jasmine.createSpy() + const pluginConfiguration = { router: routerType } + + nextjsPlugin(pluginConfiguration).onInit({ + publicApi: PUBLIC_API, + initConfiguration: INIT_CONFIGURATION, + }) + + onRumInit(callbackSpy) + + expect(callbackSpy).toHaveBeenCalledTimes(1) + expect(callbackSpy.calls.mostRecent().args[0]).toBe(pluginConfiguration) + expect(callbackSpy.calls.mostRecent().args[1]).toBe(PUBLIC_API) + }) + + it('calls onRumStart subscribers during onRumStart', () => { + const callbackSpy = jasmine.createSpy() + const mockAddEvent = jasmine.createSpy() + onRumStart(callbackSpy) + + const plugin = nextjsPlugin({ router: routerType }) + plugin.onInit({ publicApi: PUBLIC_API, initConfiguration: INIT_CONFIGURATION }) + plugin.onRumStart({ addEvent: mockAddEvent }) + + expect(callbackSpy).toHaveBeenCalledWith(mockAddEvent) + }) + + it('calls onRumStart subscriber immediately if already started', () => { + const mockAddEvent = jasmine.createSpy() + const plugin = nextjsPlugin({ router: routerType }) + plugin.onInit({ publicApi: PUBLIC_API, initConfiguration: INIT_CONFIGURATION }) + plugin.onRumStart({ addEvent: mockAddEvent }) + + const callbackSpy = jasmine.createSpy() + onRumStart(callbackSpy) + + expect(callbackSpy).toHaveBeenCalledWith(mockAddEvent) + }) + }) +}) diff --git a/packages/rum-nextjs/src/domain/nextjsPlugin.ts b/packages/rum-nextjs/src/domain/nextjsPlugin.ts new file mode 100644 index 0000000000..718572ba4e --- /dev/null +++ b/packages/rum-nextjs/src/domain/nextjsPlugin.ts @@ -0,0 +1,64 @@ +import type { RumPlugin, RumPublicApi, StartRumResult } from '@datadog/browser-rum-core' + +export interface NextjsPluginConfiguration { + router: 'app' | 'pages' +} + +export type NextjsPlugin = Pick, 'name' | 'onInit' | 'onRumStart'> + +type InitSubscriber = (configuration: NextjsPluginConfiguration, rumPublicApi: RumPublicApi) => void +type StartSubscriber = (addEvent: StartRumResult['addEvent']) => void + +let globalPublicApi: RumPublicApi | undefined +let globalConfiguration: NextjsPluginConfiguration | undefined +let globalAddEvent: StartRumResult['addEvent'] | undefined + +const onRumInitSubscribers: InitSubscriber[] = [] +const onRumStartSubscribers: StartSubscriber[] = [] + +export function nextjsPlugin(configuration: NextjsPluginConfiguration): NextjsPlugin { + return { + name: 'nextjs', + onInit({ publicApi, initConfiguration }) { + globalPublicApi = publicApi + globalConfiguration = configuration + initConfiguration.trackViewsManually = true + + for (const subscriber of onRumInitSubscribers) { + subscriber(configuration, publicApi) + } + }, + onRumStart({ addEvent }) { + globalAddEvent = addEvent + for (const subscriber of onRumStartSubscribers) { + if (addEvent) { + subscriber(addEvent) + } + } + }, + } satisfies RumPlugin +} + +export function onRumInit(callback: InitSubscriber) { + if (globalConfiguration && globalPublicApi) { + callback(globalConfiguration, globalPublicApi) + } else { + onRumInitSubscribers.push(callback) + } +} + +export function onRumStart(callback: StartSubscriber) { + if (globalAddEvent) { + callback(globalAddEvent) + } else { + onRumStartSubscribers.push(callback) + } +} + +export function resetNextjsPlugin() { + globalPublicApi = undefined + globalConfiguration = undefined + globalAddEvent = undefined + onRumInitSubscribers.length = 0 + onRumStartSubscribers.length = 0 +} diff --git a/packages/rum-nextjs/src/domain/pagesRouter/datadogRumProvider.tsx b/packages/rum-nextjs/src/domain/pagesRouter/datadogRumProvider.tsx new file mode 100644 index 0000000000..925f8b4833 --- /dev/null +++ b/packages/rum-nextjs/src/domain/pagesRouter/datadogRumProvider.tsx @@ -0,0 +1,53 @@ +'use client' + +import React, { useEffect } from 'react' +import { useRouter } from 'next/router' +import { startNextjsView } from '../startNextjsView' + +/** + * Tracks Next.js Pages Router views via `router.events`. + * + * @example + * ```tsx + * // pages/_app.tsx + * import type { AppProps } from 'next/app' + * import { datadogRum } from '@datadog/browser-rum' + * import { nextjsPlugin } from '@datadog/browser-rum-nextjs' + * import { DatadogRumProvider } from '@datadog/browser-rum-nextjs/pages-router' + * + * datadogRum.init({ + * applicationId: '', + * clientToken: '', + * plugins: [nextjsPlugin({ router: 'pages' })], + * }) + * + * export default function App({ Component, pageProps }: AppProps) { + * return ( + * + * + * + * ) + * } + * ``` + */ +export function DatadogRumProvider({ children }: { children: React.ReactNode }) { + const router = useRouter() + + useEffect(() => { + startNextjsView(router.pathname) + }, [router.pathname]) + + useEffect(() => { + const handleRouteChange = () => { + startNextjsView(router.pathname) + } + + router.events.on('routeChangeComplete', handleRouteChange) + + return () => { + router.events.off('routeChangeComplete', handleRouteChange) + } + }) + + return <>{children} +} diff --git a/packages/rum-nextjs/src/domain/startNextjsView.spec.ts b/packages/rum-nextjs/src/domain/startNextjsView.spec.ts new file mode 100644 index 0000000000..f9e773e1d5 --- /dev/null +++ b/packages/rum-nextjs/src/domain/startNextjsView.spec.ts @@ -0,0 +1,34 @@ +import type { RumInitConfiguration, RumPublicApi } from '@datadog/browser-rum-core' +import { registerCleanupTask } from '../../../core/test' +import { initializeNextjsPlugin } from '../../test/initializeNextjsPlugin' +import { nextjsPlugin, resetNextjsPlugin } from './nextjsPlugin' +import { startNextjsView } from './startNextjsView' + +describe('startNextjsView', () => { + it('calls publicApi.startView with the view name', () => { + const startViewSpy = jasmine.createSpy() + initializeNextjsPlugin({ + publicApi: { startView: startViewSpy }, + }) + + startNextjsView('/users/[id]') + + expect(startViewSpy).toHaveBeenCalledOnceWith('/users/[id]') + }) + + it('queues the view if plugin is not yet initialized', () => { + const startViewSpy = jasmine.createSpy() + + startNextjsView('/users/[id]') + + const plugin = nextjsPlugin({ router: 'app' }) + plugin.onInit({ + publicApi: { startView: startViewSpy } as Partial as RumPublicApi, + initConfiguration: {} as RumInitConfiguration, + }) + + registerCleanupTask(() => resetNextjsPlugin()) + + expect(startViewSpy).toHaveBeenCalledOnceWith('/users/[id]') + }) +}) diff --git a/packages/rum-nextjs/src/domain/startNextjsView.ts b/packages/rum-nextjs/src/domain/startNextjsView.ts new file mode 100644 index 0000000000..1723dd89f8 --- /dev/null +++ b/packages/rum-nextjs/src/domain/startNextjsView.ts @@ -0,0 +1,7 @@ +import { onRumInit } from './nextjsPlugin' + +export function startNextjsView(viewName: string) { + onRumInit((_configuration, publicApi) => { + publicApi.startView(viewName) + }) +} diff --git a/packages/rum-nextjs/src/entries/appRouter.ts b/packages/rum-nextjs/src/entries/appRouter.ts new file mode 100644 index 0000000000..664ba416ba --- /dev/null +++ b/packages/rum-nextjs/src/entries/appRouter.ts @@ -0,0 +1,2 @@ +export { DatadogRumProvider } from '../domain/appRouter/datadogRumProvider' +export { reportNextjsError } from '../domain/error/reportNextjsError' diff --git a/packages/rum-nextjs/src/entries/main.ts b/packages/rum-nextjs/src/entries/main.ts new file mode 100644 index 0000000000..78755891fb --- /dev/null +++ b/packages/rum-nextjs/src/entries/main.ts @@ -0,0 +1,3 @@ +export { nextjsPlugin } from '../domain/nextjsPlugin' +export type { NextjsPluginConfiguration, NextjsPlugin } from '../domain/nextjsPlugin' +export { addDurationVital } from '../domain/addDurationVital' diff --git a/packages/rum-nextjs/src/entries/pagesRouter.ts b/packages/rum-nextjs/src/entries/pagesRouter.ts new file mode 100644 index 0000000000..fcb84d93b2 --- /dev/null +++ b/packages/rum-nextjs/src/entries/pagesRouter.ts @@ -0,0 +1,2 @@ +export { DatadogRumProvider } from '../domain/pagesRouter/datadogRumProvider' +export { reportNextjsError } from '../domain/error/reportNextjsError' diff --git a/packages/rum-nextjs/test/initializeNextjsPlugin.ts b/packages/rum-nextjs/test/initializeNextjsPlugin.ts new file mode 100644 index 0000000000..c402da998e --- /dev/null +++ b/packages/rum-nextjs/test/initializeNextjsPlugin.ts @@ -0,0 +1,34 @@ +import type { RumInitConfiguration, RumPublicApi, StartRumResult } from '@datadog/browser-rum-core' +import { noop } from '@datadog/browser-core' +import type { NextjsPluginConfiguration } from '../src/domain/nextjsPlugin' +import { nextjsPlugin, resetNextjsPlugin } from '../src/domain/nextjsPlugin' +import { registerCleanupTask } from '../../core/test' + +export function initializeNextjsPlugin({ + configuration = { router: 'app' as const }, + initConfiguration = {}, + publicApi = {}, + addEvent = noop, +}: { + configuration?: NextjsPluginConfiguration + initConfiguration?: Partial + publicApi?: Partial + addEvent?: StartRumResult['addEvent'] +} = {}) { + resetNextjsPlugin() + + const plugin = nextjsPlugin(configuration) + + plugin.onInit({ + publicApi: publicApi as RumPublicApi, + initConfiguration: initConfiguration as RumInitConfiguration, + }) + + plugin.onRumStart({ + addEvent, + }) + + registerCleanupTask(() => { + resetNextjsPlugin() + }) +} diff --git a/packages/rum-react/src/domain/performance/reactComponentTracker.spec.tsx b/packages/rum-react/src/domain/performance/reactComponentTracker.spec.tsx index d15ce87f3e..ef8e15233a 100644 --- a/packages/rum-react/src/domain/performance/reactComponentTracker.spec.tsx +++ b/packages/rum-react/src/domain/performance/reactComponentTracker.spec.tsx @@ -57,6 +57,19 @@ describe('UNSTABLE_ReactComponentTracker', () => { }) }) + it('should use injected addDurationVital when provided as prop', () => { + const injectedSpy = jasmine.createSpy() + + appendComponent( + + + + ) + + expect(injectedSpy).toHaveBeenCalledTimes(1) + expect(injectedSpy.calls.mostRecent().args[0]).toBe('reactComponentRender') + }) + it('should call addDurationVital on rerender', () => { const addDurationVitalSpy = jasmine.createSpy() initializeReactPlugin({ diff --git a/packages/rum-react/src/domain/performance/reactComponentTracker.tsx b/packages/rum-react/src/domain/performance/reactComponentTracker.tsx index a9904a5705..7e575a12c3 100644 --- a/packages/rum-react/src/domain/performance/reactComponentTracker.tsx +++ b/packages/rum-react/src/domain/performance/reactComponentTracker.tsx @@ -1,6 +1,7 @@ +import type { RumPublicApi } from '@datadog/browser-rum-core' import * as React from 'react' import { createTimer } from './timer' -import { addDurationVital } from './addDurationVital' +import { addDurationVital as defaultAddDurationVital } from './addDurationVital' /** * Track the performance of a React component. @@ -11,9 +12,11 @@ import { addDurationVital } from './addDurationVital' export const UNSTABLE_ReactComponentTracker = ({ name: componentName, children, + addDurationVital = defaultAddDurationVital, }: { name: string children?: React.ReactNode + addDurationVital?: RumPublicApi['addDurationVital'] }) => { const isFirstRender = React.useRef(true) diff --git a/scripts/build/build-test-apps.ts b/scripts/build/build-test-apps.ts index 1af38f9102..f69051c00a 100644 --- a/scripts/build/build-test-apps.ts +++ b/scripts/build/build-test-apps.ts @@ -20,6 +20,8 @@ runMain(async () => { buildApp('test/apps/react-shopist-like') await buildReactRouterv7App() await buildExtensions() + buildApp('test/apps/nextjs-app-router') + buildApp('test/apps/nextjs-pages-router') printLog('Test apps and extensions built successfully.') }) diff --git a/test/apps/nextjs-app-router/.gitignore b/test/apps/nextjs-app-router/.gitignore new file mode 100644 index 0000000000..875dc23369 --- /dev/null +++ b/test/apps/nextjs-app-router/.gitignore @@ -0,0 +1,3 @@ +.next +node_modules +.yarn/* diff --git a/test/apps/nextjs-app-router/app/error-test/error.tsx b/test/apps/nextjs-app-router/app/error-test/error.tsx new file mode 100644 index 0000000000..2377503d4b --- /dev/null +++ b/test/apps/nextjs-app-router/app/error-test/error.tsx @@ -0,0 +1,18 @@ +'use client' + +import { useEffect } from 'react' +import { reportNextjsError } from '@datadog/browser-rum-nextjs/app-router' + +export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) { + useEffect(() => { + reportNextjsError(error, reset) + }, [error, reset]) + + return ( +
+

Something went wrong!

+

{error.message}

+ +
+ ) +} diff --git a/test/apps/nextjs-app-router/app/error-test/page.tsx b/test/apps/nextjs-app-router/app/error-test/page.tsx new file mode 100644 index 0000000000..57569af967 --- /dev/null +++ b/test/apps/nextjs-app-router/app/error-test/page.tsx @@ -0,0 +1,50 @@ +'use client' + +import { useState } from 'react' +import Link from 'next/link' + +function ComponentWithErrorButton() { + const [shouldError, setShouldError] = useState(false) + + if (shouldError) { + throw new Error('Error triggered by button click') + } + + return ( +
+

Click button to trigger error

+ +
+ ) +} + +export default function ErrorTestPage() { + const throwAsyncError = async () => { + await new Promise((resolve) => setTimeout(resolve, 100)) + throw new Error('Test asynchronous error') + } + + const throwUnhandledRejection = () => { + Promise.reject(new Error('Test unhandled promise rejection')) + } + + return ( +
+ ← Back to Home +

Error Testing

+ +
+

Error Boundary (Next.js error.tsx)

+ +
+ +
+

Other Error Types

+ + +
+
+ ) +} diff --git a/test/apps/nextjs-app-router/app/guides/[...slug]/page.tsx b/test/apps/nextjs-app-router/app/guides/[...slug]/page.tsx new file mode 100644 index 0000000000..d94c451b8d --- /dev/null +++ b/test/apps/nextjs-app-router/app/guides/[...slug]/page.tsx @@ -0,0 +1,13 @@ +import Link from 'next/link' + +export default async function GuidesPage({ params }: { params: Promise<{ slug: string[] }> }) { + const { slug } = await params + + return ( +
+ ← Back to Home +

Guides: {slug.join('/')}

+

This is a catch-all route testing slug normalization.

+
+ ) +} diff --git a/test/apps/nextjs-app-router/app/layout.tsx b/test/apps/nextjs-app-router/app/layout.tsx new file mode 100644 index 0000000000..e4a1d2e274 --- /dev/null +++ b/test/apps/nextjs-app-router/app/layout.tsx @@ -0,0 +1,24 @@ +import type { Metadata } from 'next' +import { RumProvider } from './providers' + +export const metadata: Metadata = { + title: 'Next.js App Router Test', + description: 'Test app for Datadog RUM Next.js integration', +} + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + +
{children}
+
+ + + ) +} diff --git a/test/apps/nextjs-app-router/app/page.tsx b/test/apps/nextjs-app-router/app/page.tsx new file mode 100644 index 0000000000..a568f21f5f --- /dev/null +++ b/test/apps/nextjs-app-router/app/page.tsx @@ -0,0 +1,26 @@ +import Link from 'next/link' + +export default function HomePage() { + return ( +
+

Home

+
    +
  • + Go to User 42 +
  • +
  • + Go to User 123 +
  • +
  • + Go to Tracked Component +
  • +
  • + Go to Guides +
  • +
  • + Go to Error Test +
  • +
+
+ ) +} diff --git a/test/apps/nextjs-app-router/app/providers.tsx b/test/apps/nextjs-app-router/app/providers.tsx new file mode 100644 index 0000000000..9888ed944a --- /dev/null +++ b/test/apps/nextjs-app-router/app/providers.tsx @@ -0,0 +1,20 @@ +'use client' + +import type { ReactNode } from 'react' +import { datadogRum } from '@datadog/browser-rum' +import { nextjsPlugin } from '@datadog/browser-rum-nextjs' +import { DatadogRumProvider } from '@datadog/browser-rum-nextjs/app-router' + +if (typeof window !== 'undefined') { + const config = (window as any).RUM_CONFIGURATION + if (config) { + datadogRum.init({ + ...config, + plugins: [nextjsPlugin({ router: 'app' }), ...(config.plugins || [])], + }) + } +} + +export function RumProvider({ children }: { children: ReactNode }) { + return {children} +} diff --git a/test/apps/nextjs-app-router/app/tracked/page.tsx b/test/apps/nextjs-app-router/app/tracked/page.tsx new file mode 100644 index 0000000000..7e17f81b4e --- /dev/null +++ b/test/apps/nextjs-app-router/app/tracked/page.tsx @@ -0,0 +1,17 @@ +'use client' + +import Link from 'next/link' +import { UNSTABLE_ReactComponentTracker as ReactComponentTracker } from '@datadog/browser-rum-react' +import { addDurationVital } from '@datadog/browser-rum-nextjs' + +export default function TrackedPage() { + return ( +
+ ← Back to Home + +

Component Tracker

+

This component is tracked for performance metrics.

+
+
+ ) +} diff --git a/test/apps/nextjs-app-router/app/user/[id]/page.tsx b/test/apps/nextjs-app-router/app/user/[id]/page.tsx new file mode 100644 index 0000000000..5a496c9ba4 --- /dev/null +++ b/test/apps/nextjs-app-router/app/user/[id]/page.tsx @@ -0,0 +1,13 @@ +import Link from 'next/link' + +export default async function UserPage({ params }: { params: Promise<{ id: string }> }) { + const { id } = await params + + return ( +
+ ← Back to Home +

User {id}

+

This is a dynamic route testing view name normalization.

+
+ ) +} diff --git a/test/apps/nextjs-app-router/next-env.d.ts b/test/apps/nextjs-app-router/next-env.d.ts new file mode 100644 index 0000000000..a3e4680c77 --- /dev/null +++ b/test/apps/nextjs-app-router/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +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. diff --git a/test/apps/nextjs-app-router/next.config.js b/test/apps/nextjs-app-router/next.config.js new file mode 100644 index 0000000000..c77754a91e --- /dev/null +++ b/test/apps/nextjs-app-router/next.config.js @@ -0,0 +1,8 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + turbopack: { + root: __dirname, + }, +} + +module.exports = nextConfig diff --git a/test/apps/nextjs-app-router/package.json b/test/apps/nextjs-app-router/package.json new file mode 100644 index 0000000000..fb1e961dfb --- /dev/null +++ b/test/apps/nextjs-app-router/package.json @@ -0,0 +1,35 @@ +{ + "name": "nextjs-app-router", + "private": true, + "scripts": { + "dev": "next dev --port 3000", + "build": "next build", + "start": "next start --port 3000" + }, + "dependencies": { + "@datadog/browser-rum": "file:../../../packages/rum/package.tgz", + "@datadog/browser-rum-nextjs": "file:../../../packages/rum-nextjs/package.tgz", + "@datadog/browser-rum-react": "file:../../../packages/rum-react/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-react": "file:../../../packages/rum-react/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" + } +} diff --git a/test/apps/nextjs-app-router/tsconfig.json b/test/apps/nextjs-app-router/tsconfig.json new file mode 100644 index 0000000000..f22e40d712 --- /dev/null +++ b/test/apps/nextjs-app-router/tsconfig.json @@ -0,0 +1,27 @@ +{ + "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", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/test/apps/nextjs-app-router/yarn.lock b/test/apps/nextjs-app-router/yarn.lock new file mode 100644 index 0000000000..d1d6e88ce5 --- /dev/null +++ b/test/apps/nextjs-app-router/yarn.lock @@ -0,0 +1,731 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@datadog/browser-core@file:../../../packages/core/package.tgz::locator=nextjs-app-router%40workspace%3A.": + version: 6.27.1 + resolution: "@datadog/browser-core@file:../../../packages/core/package.tgz#../../../packages/core/package.tgz::hash=576c53&locator=nextjs-app-router%40workspace%3A." + checksum: 10c0/1d30de7280afb817c4a713944d8114068b6beb8c191baffffb2fae5d0937c3f74b3378b2ad2d9e18511f5037986fa361254850c3196ae09ec2a97bc807db2e7c + languageName: node + linkType: hard + +"@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz::locator=nextjs-app-router%40workspace%3A.": + version: 6.27.1 + resolution: "@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz#../../../packages/rum-core/package.tgz::hash=33952e&locator=nextjs-app-router%40workspace%3A." + dependencies: + "@datadog/browser-core": "npm:6.27.1" + checksum: 10c0/73d6e63ed61b7c6cdab8bd2a9c397a52eb48f7fce0db61cfe9914f32a32a7ec3e8b6c21fdd126596c28c27169b718a8290196c653215800c027142068bdb23cb + languageName: node + linkType: hard + +"@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz::locator=nextjs-app-router%40workspace%3A.": + version: 0.0.0 + resolution: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz#../../../packages/rum-nextjs/package.tgz::hash=e7b1d6&locator=nextjs-app-router%40workspace%3A." + dependencies: + "@datadog/browser-core": "npm:6.27.1" + "@datadog/browser-rum-core": "npm:6.27.1" + peerDependencies: + next: ">=12.0.0" + react: ">=18.0.0" + peerDependenciesMeta: + next: + optional: true + react: + optional: true + checksum: 10c0/44a052012d5bc27c8afcc8d2c96f79d2857d2eabcf5fc57c0684c09240f67de26d814fd212cfffa1a84340bf17d8c87eb32792d2e12301c6e95aaf0bf21b7250 + languageName: node + linkType: hard + +"@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz::locator=nextjs-app-router%40workspace%3A.": + version: 6.27.1 + resolution: "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz#../../../packages/rum-react/package.tgz::hash=d63e04&locator=nextjs-app-router%40workspace%3A." + dependencies: + "@datadog/browser-core": "npm:6.27.1" + "@datadog/browser-rum-core": "npm:6.27.1" + peerDependencies: + react: 18 || 19 + react-router: 6 || 7 + react-router-dom: 6 || 7 + peerDependenciesMeta: + "@datadog/browser-rum": + optional: true + "@datadog/browser-rum-slim": + optional: true + react: + optional: true + react-router: + optional: true + react-router-dom: + optional: true + checksum: 10c0/53dec6173c8d968b771321cb54e95f67578fb1d65d41401110d745e79fa4fe152273ed7de3211305571bfe0adb87e5cd530f7ea5279e4df5eb03e6b4d0975d15 + languageName: node + linkType: hard + +"@datadog/browser-rum@file:../../../packages/rum/package.tgz::locator=nextjs-app-router%40workspace%3A.": + version: 6.27.1 + resolution: "@datadog/browser-rum@file:../../../packages/rum/package.tgz#../../../packages/rum/package.tgz::hash=f49d49&locator=nextjs-app-router%40workspace%3A." + dependencies: + "@datadog/browser-core": "npm:6.27.1" + "@datadog/browser-rum-core": "npm:6.27.1" + peerDependencies: + "@datadog/browser-logs": 6.27.1 + peerDependenciesMeta: + "@datadog/browser-logs": + optional: true + checksum: 10c0/47da488faad9c116b054b5f289f96d2f4265bd125c828db3a6e416f2ff1fa39485883d54633446fa96e2eb5d9840019bbe51eef8d346fb2572917c76560de513 + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.7.0": + version: 1.8.1 + resolution: "@emnapi/runtime@npm:1.8.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/f4929d75e37aafb24da77d2f58816761fe3f826aad2e37fa6d4421dac9060cbd5098eea1ac3c9ecc4526b89deb58153852fa432f87021dc57863f2ff726d713f + languageName: node + linkType: hard + +"@img/colour@npm:^1.0.0": + version: 1.0.0 + resolution: "@img/colour@npm:1.0.0" + checksum: 10c0/02261719c1e0d7aa5a2d585981954f2ac126f0c432400aa1a01b925aa2c41417b7695da8544ee04fd29eba7ecea8eaf9b8bef06f19dc8faba78f94eeac40667d + languageName: node + linkType: hard + +"@img/sharp-darwin-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-darwin-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-darwin-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-arm64": + optional: true + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-darwin-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-darwin-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-darwin-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-x64": + optional: true + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-arm@npm:1.2.4" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-ppc64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.4" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-riscv64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-riscv64@npm:1.2.4" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-s390x@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.4" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-x64@npm:1.2.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linux-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-arm@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-arm@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-arm": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-arm": + optional: true + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-ppc64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-ppc64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-ppc64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-ppc64": + optional: true + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-riscv64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-riscv64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-riscv64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-riscv64": + optional: true + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-s390x@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-s390x@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-s390x": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-s390x": + optional: true + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linuxmusl-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-wasm32@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-wasm32@npm:0.34.5" + dependencies: + "@emnapi/runtime": "npm:^1.7.0" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@img/sharp-win32-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-arm64@npm:0.34.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-win32-ia32@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-ia32@npm:0.34.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@img/sharp-win32-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-x64@npm:0.34.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@next/env@npm:16.1.6": + version: 16.1.6 + resolution: "@next/env@npm:16.1.6" + checksum: 10c0/ed7023edb94b9b2e5da3f9c99d08b614da9757c1edd0ecec792fce4d336b4f0c64db1a84955e07cfbd848b9e61c4118fff28f4098cd7b0a7f97814a90565ebe6 + languageName: node + linkType: hard + +"@next/swc-darwin-arm64@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-darwin-arm64@npm:16.1.6" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@next/swc-darwin-x64@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-darwin-x64@npm:16.1.6" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@next/swc-linux-arm64-gnu@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-linux-arm64-gnu@npm:16.1.6" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@next/swc-linux-arm64-musl@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-linux-arm64-musl@npm:16.1.6" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@next/swc-linux-x64-gnu@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-linux-x64-gnu@npm:16.1.6" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@next/swc-linux-x64-musl@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-linux-x64-musl@npm:16.1.6" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@next/swc-win32-arm64-msvc@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-win32-arm64-msvc@npm:16.1.6" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@next/swc-win32-x64-msvc@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-win32-x64-msvc@npm:16.1.6" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@swc/helpers@npm:0.5.15": + version: 0.5.15 + resolution: "@swc/helpers@npm:0.5.15" + dependencies: + tslib: "npm:^2.8.0" + checksum: 10c0/33002f74f6f885f04c132960835fdfc474186983ea567606db62e86acd0680ca82f34647e8e610f4e1e422d1c16fce729dde22cd3b797ab1fd9061a825dabca4 + languageName: node + linkType: hard + +"@types/node@npm:22.16.0": + version: 22.16.0 + resolution: "@types/node@npm:22.16.0" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10c0/6219b521062f6c38d4d85ebd25807bd7f2bc703a5acba24e2c6716938d9d6cefd6fafd7b5156f61580eb58a0d82e8921751b778655675389631d813e5f261c03 + languageName: node + linkType: hard + +"@types/react-dom@npm:19.2.3": + version: 19.2.3 + resolution: "@types/react-dom@npm:19.2.3" + peerDependencies: + "@types/react": ^19.2.0 + checksum: 10c0/b486ebe0f4e2fb35e2e108df1d8fc0927ca5d6002d5771e8a739de11239fe62d0e207c50886185253c99eb9dedfeeb956ea7429e5ba17f6693c7acb4c02f8cd1 + languageName: node + linkType: hard + +"@types/react@npm:19.2.8": + version: 19.2.8 + resolution: "@types/react@npm:19.2.8" + dependencies: + csstype: "npm:^3.2.2" + checksum: 10c0/832834998c4ee971fca72ecf1eb95dc924ad3931a2112c687a4dae498aabd115c5fa4db09186853e34a646226b0223808c8f867df03d17601168f9cf119448de + languageName: node + linkType: hard + +"baseline-browser-mapping@npm:^2.8.3": + version: 2.10.0 + resolution: "baseline-browser-mapping@npm:2.10.0" + bin: + baseline-browser-mapping: dist/cli.cjs + checksum: 10c0/da9c3ec0fcd7f325226a47d2142794d41706b6e0a405718a2c15410bbdb72aacadd65738bedef558c6f1b106ed19458cb25b06f63b66df2c284799905dbbd003 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001579": + version: 1.0.30001770 + resolution: "caniuse-lite@npm:1.0.30001770" + checksum: 10c0/02d15a8b723af65318cb4d888a52bb090076898da7b0de99e8676d537f8d1d2ae4797e81518e1e30cbfe84c33b048c322e8bfafc5b23cfee8defb0d2bf271149 + languageName: node + linkType: hard + +"client-only@npm:0.0.1": + version: 0.0.1 + resolution: "client-only@npm:0.0.1" + checksum: 10c0/9d6cfd0c19e1c96a434605added99dff48482152af791ec4172fb912a71cff9027ff174efd8cdb2160cc7f377543e0537ffc462d4f279bc4701de3f2a3c4b358 + languageName: node + linkType: hard + +"csstype@npm:^3.2.2": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce + languageName: node + linkType: hard + +"detect-libc@npm:^2.1.2": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.6": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + +"next@npm:16.1.6": + version: 16.1.6 + resolution: "next@npm:16.1.6" + dependencies: + "@next/env": "npm:16.1.6" + "@next/swc-darwin-arm64": "npm:16.1.6" + "@next/swc-darwin-x64": "npm:16.1.6" + "@next/swc-linux-arm64-gnu": "npm:16.1.6" + "@next/swc-linux-arm64-musl": "npm:16.1.6" + "@next/swc-linux-x64-gnu": "npm:16.1.6" + "@next/swc-linux-x64-musl": "npm:16.1.6" + "@next/swc-win32-arm64-msvc": "npm:16.1.6" + "@next/swc-win32-x64-msvc": "npm:16.1.6" + "@swc/helpers": "npm:0.5.15" + baseline-browser-mapping: "npm:^2.8.3" + caniuse-lite: "npm:^1.0.30001579" + postcss: "npm:8.4.31" + sharp: "npm:^0.34.4" + styled-jsx: "npm:5.1.6" + peerDependencies: + "@opentelemetry/api": ^1.1.0 + "@playwright/test": ^1.51.1 + babel-plugin-react-compiler: "*" + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + dependenciesMeta: + "@next/swc-darwin-arm64": + optional: true + "@next/swc-darwin-x64": + optional: true + "@next/swc-linux-arm64-gnu": + optional: true + "@next/swc-linux-arm64-musl": + optional: true + "@next/swc-linux-x64-gnu": + optional: true + "@next/swc-linux-x64-musl": + optional: true + "@next/swc-win32-arm64-msvc": + optional: true + "@next/swc-win32-x64-msvc": + optional: true + sharp: + optional: true + peerDependenciesMeta: + "@opentelemetry/api": + optional: true + "@playwright/test": + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + bin: + next: dist/bin/next + checksum: 10c0/543766bf879bb5a5d454dc18cb302953270a92efba1d01dd028ea83c64b69573ce7d6e6c3759ecbaabec0a84131b0237263c24d1ccd7c8a97205e776dcd34e0b + languageName: node + linkType: hard + +"nextjs-app-router@workspace:.": + version: 0.0.0-use.local + resolution: "nextjs-app-router@workspace:." + dependencies: + "@datadog/browser-rum": "file:../../../packages/rum/package.tgz" + "@datadog/browser-rum-nextjs": "file:../../../packages/rum-nextjs/package.tgz" + "@datadog/browser-rum-react": "file:../../../packages/rum-react/package.tgz" + "@types/node": "npm:22.16.0" + "@types/react": "npm:19.2.8" + "@types/react-dom": "npm:19.2.3" + next: "npm:16.1.6" + react: "npm:19.2.3" + react-dom: "npm:19.2.3" + typescript: "npm:5.9.3" + languageName: unknown + linkType: soft + +"picocolors@npm:^1.0.0": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"postcss@npm:8.4.31": + version: 8.4.31 + resolution: "postcss@npm:8.4.31" + dependencies: + nanoid: "npm:^3.3.6" + picocolors: "npm:^1.0.0" + source-map-js: "npm:^1.0.2" + checksum: 10c0/748b82e6e5fc34034dcf2ae88ea3d11fd09f69b6c50ecdd3b4a875cfc7cdca435c958b211e2cb52355422ab6fccb7d8f2f2923161d7a1b281029e4a913d59acf + languageName: node + linkType: hard + +"react-dom@npm:19.2.3": + version: 19.2.3 + resolution: "react-dom@npm:19.2.3" + dependencies: + scheduler: "npm:^0.27.0" + peerDependencies: + react: ^19.2.3 + checksum: 10c0/dc43f7ede06f46f3acc16ee83107c925530de9b91d1d0b3824583814746ff4c498ea64fd65cd83aba363205268adff52e2827c582634ae7b15069deaeabc4892 + languageName: node + linkType: hard + +"react@npm:19.2.3": + version: 19.2.3 + resolution: "react@npm:19.2.3" + checksum: 10c0/094220b3ba3a76c1b668f972ace1dd15509b157aead1b40391d1c8e657e720c201d9719537375eff08f5e0514748c0319063392a6f000e31303aafc4471f1436 + languageName: node + linkType: hard + +"scheduler@npm:^0.27.0": + version: 0.27.0 + resolution: "scheduler@npm:0.27.0" + checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452 + languageName: node + linkType: hard + +"semver@npm:^7.7.3": + version: 7.7.4 + resolution: "semver@npm:7.7.4" + bin: + semver: bin/semver.js + checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 + languageName: node + linkType: hard + +"sharp@npm:^0.34.4": + version: 0.34.5 + resolution: "sharp@npm:0.34.5" + dependencies: + "@img/colour": "npm:^1.0.0" + "@img/sharp-darwin-arm64": "npm:0.34.5" + "@img/sharp-darwin-x64": "npm:0.34.5" + "@img/sharp-libvips-darwin-arm64": "npm:1.2.4" + "@img/sharp-libvips-darwin-x64": "npm:1.2.4" + "@img/sharp-libvips-linux-arm": "npm:1.2.4" + "@img/sharp-libvips-linux-arm64": "npm:1.2.4" + "@img/sharp-libvips-linux-ppc64": "npm:1.2.4" + "@img/sharp-libvips-linux-riscv64": "npm:1.2.4" + "@img/sharp-libvips-linux-s390x": "npm:1.2.4" + "@img/sharp-libvips-linux-x64": "npm:1.2.4" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4" + "@img/sharp-linux-arm": "npm:0.34.5" + "@img/sharp-linux-arm64": "npm:0.34.5" + "@img/sharp-linux-ppc64": "npm:0.34.5" + "@img/sharp-linux-riscv64": "npm:0.34.5" + "@img/sharp-linux-s390x": "npm:0.34.5" + "@img/sharp-linux-x64": "npm:0.34.5" + "@img/sharp-linuxmusl-arm64": "npm:0.34.5" + "@img/sharp-linuxmusl-x64": "npm:0.34.5" + "@img/sharp-wasm32": "npm:0.34.5" + "@img/sharp-win32-arm64": "npm:0.34.5" + "@img/sharp-win32-ia32": "npm:0.34.5" + "@img/sharp-win32-x64": "npm:0.34.5" + detect-libc: "npm:^2.1.2" + semver: "npm:^7.7.3" + dependenciesMeta: + "@img/sharp-darwin-arm64": + optional: true + "@img/sharp-darwin-x64": + optional: true + "@img/sharp-libvips-darwin-arm64": + optional: true + "@img/sharp-libvips-darwin-x64": + optional: true + "@img/sharp-libvips-linux-arm": + optional: true + "@img/sharp-libvips-linux-arm64": + optional: true + "@img/sharp-libvips-linux-ppc64": + optional: true + "@img/sharp-libvips-linux-riscv64": + optional: true + "@img/sharp-libvips-linux-s390x": + optional: true + "@img/sharp-libvips-linux-x64": + optional: true + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + "@img/sharp-libvips-linuxmusl-x64": + optional: true + "@img/sharp-linux-arm": + optional: true + "@img/sharp-linux-arm64": + optional: true + "@img/sharp-linux-ppc64": + optional: true + "@img/sharp-linux-riscv64": + optional: true + "@img/sharp-linux-s390x": + optional: true + "@img/sharp-linux-x64": + optional: true + "@img/sharp-linuxmusl-arm64": + optional: true + "@img/sharp-linuxmusl-x64": + optional: true + "@img/sharp-wasm32": + optional: true + "@img/sharp-win32-arm64": + optional: true + "@img/sharp-win32-ia32": + optional: true + "@img/sharp-win32-x64": + optional: true + checksum: 10c0/fd79e29df0597a7d5704b8461c51f944ead91a5243691697be6e8243b966402beda53ddc6f0a53b96ea3cb8221f0b244aa588114d3ebf8734fb4aefd41ab802f + languageName: node + linkType: hard + +"source-map-js@npm:^1.0.2": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + +"styled-jsx@npm:5.1.6": + version: 5.1.6 + resolution: "styled-jsx@npm:5.1.6" + dependencies: + client-only: "npm:0.0.1" + peerDependencies: + react: ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + peerDependenciesMeta: + "@babel/core": + optional: true + babel-plugin-macros: + optional: true + checksum: 10c0/ace50e7ea5ae5ae6a3b65a50994c51fca6ae7df9c7ecfd0104c36be0b4b3a9c5c1a2374d16e2a11e256d0b20be6d47256d768ecb4f91ab390f60752a075780f5 + languageName: node + linkType: hard + +"tslib@npm:^2.4.0, tslib@npm:^2.8.0": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + +"typescript@npm:5.9.3": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A5.9.3#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + languageName: node + linkType: hard + +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10c0/c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04 + languageName: node + linkType: hard diff --git a/test/apps/nextjs-pages-router/.gitignore b/test/apps/nextjs-pages-router/.gitignore new file mode 100644 index 0000000000..875dc23369 --- /dev/null +++ b/test/apps/nextjs-pages-router/.gitignore @@ -0,0 +1,3 @@ +.next +node_modules +.yarn/* diff --git a/test/apps/nextjs-pages-router/next-env.d.ts b/test/apps/nextjs-pages-router/next-env.d.ts new file mode 100644 index 0000000000..83311bdfa2 --- /dev/null +++ b/test/apps/nextjs-pages-router/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +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. diff --git a/test/apps/nextjs-pages-router/next.config.js b/test/apps/nextjs-pages-router/next.config.js new file mode 100644 index 0000000000..c77754a91e --- /dev/null +++ b/test/apps/nextjs-pages-router/next.config.js @@ -0,0 +1,8 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + turbopack: { + root: __dirname, + }, +} + +module.exports = nextConfig diff --git a/test/apps/nextjs-pages-router/package.json b/test/apps/nextjs-pages-router/package.json new file mode 100644 index 0000000000..aa56afdddc --- /dev/null +++ b/test/apps/nextjs-pages-router/package.json @@ -0,0 +1,35 @@ +{ + "name": "nextjs-pages-router", + "private": true, + "scripts": { + "dev": "next dev --port 3001", + "build": "next build", + "start": "next start --port 3001" + }, + "dependencies": { + "@datadog/browser-rum": "file:../../../packages/rum/package.tgz", + "@datadog/browser-rum-nextjs": "file:../../../packages/rum-nextjs/package.tgz", + "@datadog/browser-rum-react": "file:../../../packages/rum-react/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-react": "file:../../../packages/rum-react/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" + } +} diff --git a/test/apps/nextjs-pages-router/pages/_app.tsx b/test/apps/nextjs-pages-router/pages/_app.tsx new file mode 100644 index 0000000000..f2b1e816ce --- /dev/null +++ b/test/apps/nextjs-pages-router/pages/_app.tsx @@ -0,0 +1,29 @@ +import type { AppProps } from 'next/app' +import { datadogRum } from '@datadog/browser-rum' +import { nextjsPlugin } from '@datadog/browser-rum-nextjs' +import { DatadogRumProvider } from '@datadog/browser-rum-nextjs/pages-router' + +if (typeof window !== 'undefined') { + const config = (window as any).RUM_CONFIGURATION + if (config) { + datadogRum.init({ + ...config, + plugins: [nextjsPlugin({ router: 'pages' }), ...(config.plugins || [])], + }) + } +} + +export default function App({ Component, pageProps }: AppProps) { + return ( + + +
+ +
+
+ ) +} diff --git a/test/apps/nextjs-pages-router/pages/_document.tsx b/test/apps/nextjs-pages-router/pages/_document.tsx new file mode 100644 index 0000000000..ab0f8f21f1 --- /dev/null +++ b/test/apps/nextjs-pages-router/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} diff --git a/test/apps/nextjs-pages-router/pages/_error.tsx b/test/apps/nextjs-pages-router/pages/_error.tsx new file mode 100644 index 0000000000..768d28fc64 --- /dev/null +++ b/test/apps/nextjs-pages-router/pages/_error.tsx @@ -0,0 +1,30 @@ +import type { NextPageContext } from 'next' +import { reportNextjsError } from '@datadog/browser-rum-nextjs/pages-router' + +interface ErrorPageProps { + statusCode?: number + message?: string +} + +function ErrorPage({ statusCode, message }: ErrorPageProps) { + return ( +
+

{statusCode || 'Error'}

+

+ {message || (statusCode ? `A ${statusCode} error occurred on the server` : 'An error occurred on the client')} +

+
+ ) +} + +ErrorPage.getInitialProps = ({ res, err }: NextPageContext) => { + const statusCode = res ? res.statusCode : err ? err.statusCode : 404 + + if (err) { + reportNextjsError(err, statusCode ?? 500) + } + + return { statusCode, message: err?.message } +} + +export default ErrorPage diff --git a/test/apps/nextjs-pages-router/pages/error-test.tsx b/test/apps/nextjs-pages-router/pages/error-test.tsx new file mode 100644 index 0000000000..9bfb82f2aa --- /dev/null +++ b/test/apps/nextjs-pages-router/pages/error-test.tsx @@ -0,0 +1,25 @@ +import Link from 'next/link' + +export default function ErrorTestPage() { + const throwAsyncError = async () => { + await new Promise((resolve) => setTimeout(resolve, 100)) + throw new Error('Test asynchronous error') + } + + const throwUnhandledRejection = () => { + Promise.reject(new Error('Test unhandled promise rejection')) + } + + return ( +
+ ← Back to Home +

Error Testing

+ +
+

Error Types

+ + +
+
+ ) +} diff --git a/test/apps/nextjs-pages-router/pages/guides/[...slug].tsx b/test/apps/nextjs-pages-router/pages/guides/[...slug].tsx new file mode 100644 index 0000000000..8be9f2db69 --- /dev/null +++ b/test/apps/nextjs-pages-router/pages/guides/[...slug].tsx @@ -0,0 +1,14 @@ +import Link from 'next/link' +import { useRouter } from 'next/router' + +export default function GuidesPage() { + const { slug } = useRouter().query + + return ( +
+ ← Back to Home +

Guides: {Array.isArray(slug) ? slug.join('/') : slug}

+

This is a catch-all route testing slug normalization.

+
+ ) +} diff --git a/test/apps/nextjs-pages-router/pages/index.tsx b/test/apps/nextjs-pages-router/pages/index.tsx new file mode 100644 index 0000000000..a568f21f5f --- /dev/null +++ b/test/apps/nextjs-pages-router/pages/index.tsx @@ -0,0 +1,26 @@ +import Link from 'next/link' + +export default function HomePage() { + return ( +
+

Home

+
    +
  • + Go to User 42 +
  • +
  • + Go to User 123 +
  • +
  • + Go to Tracked Component +
  • +
  • + Go to Guides +
  • +
  • + Go to Error Test +
  • +
+
+ ) +} diff --git a/test/apps/nextjs-pages-router/pages/tracked.tsx b/test/apps/nextjs-pages-router/pages/tracked.tsx new file mode 100644 index 0000000000..70adc02610 --- /dev/null +++ b/test/apps/nextjs-pages-router/pages/tracked.tsx @@ -0,0 +1,15 @@ +import Link from 'next/link' +import { UNSTABLE_ReactComponentTracker as ReactComponentTracker } from '@datadog/browser-rum-react' +import { addDurationVital } from '@datadog/browser-rum-nextjs' + +export default function TrackedPage() { + return ( +
+ ← Back to Home + +

Component Tracker

+

This component is tracked for performance metrics.

+
+
+ ) +} diff --git a/test/apps/nextjs-pages-router/pages/user/[id].tsx b/test/apps/nextjs-pages-router/pages/user/[id].tsx new file mode 100644 index 0000000000..05af0ef7d5 --- /dev/null +++ b/test/apps/nextjs-pages-router/pages/user/[id].tsx @@ -0,0 +1,15 @@ +import Link from 'next/link' +import { useRouter } from 'next/router' + +export default function UserPage() { + const router = useRouter() + const { id } = router.query + + return ( +
+ ← Back to Home +

User {id}

+

This is a dynamic route testing view name normalization.

+
+ ) +} diff --git a/test/apps/nextjs-pages-router/tsconfig.json b/test/apps/nextjs-pages-router/tsconfig.json new file mode 100644 index 0000000000..f22e40d712 --- /dev/null +++ b/test/apps/nextjs-pages-router/tsconfig.json @@ -0,0 +1,27 @@ +{ + "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", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/test/apps/nextjs-pages-router/yarn.lock b/test/apps/nextjs-pages-router/yarn.lock new file mode 100644 index 0000000000..50bd84dd17 --- /dev/null +++ b/test/apps/nextjs-pages-router/yarn.lock @@ -0,0 +1,731 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@datadog/browser-core@file:../../../packages/core/package.tgz::locator=nextjs-pages-router%40workspace%3A.": + version: 6.27.1 + resolution: "@datadog/browser-core@file:../../../packages/core/package.tgz#../../../packages/core/package.tgz::hash=576c53&locator=nextjs-pages-router%40workspace%3A." + checksum: 10c0/1d30de7280afb817c4a713944d8114068b6beb8c191baffffb2fae5d0937c3f74b3378b2ad2d9e18511f5037986fa361254850c3196ae09ec2a97bc807db2e7c + languageName: node + linkType: hard + +"@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz::locator=nextjs-pages-router%40workspace%3A.": + version: 6.27.1 + resolution: "@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz#../../../packages/rum-core/package.tgz::hash=33952e&locator=nextjs-pages-router%40workspace%3A." + dependencies: + "@datadog/browser-core": "npm:6.27.1" + checksum: 10c0/73d6e63ed61b7c6cdab8bd2a9c397a52eb48f7fce0db61cfe9914f32a32a7ec3e8b6c21fdd126596c28c27169b718a8290196c653215800c027142068bdb23cb + languageName: node + linkType: hard + +"@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz::locator=nextjs-pages-router%40workspace%3A.": + version: 0.0.0 + resolution: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz#../../../packages/rum-nextjs/package.tgz::hash=e7b1d6&locator=nextjs-pages-router%40workspace%3A." + dependencies: + "@datadog/browser-core": "npm:6.27.1" + "@datadog/browser-rum-core": "npm:6.27.1" + peerDependencies: + next: ">=12.0.0" + react: ">=18.0.0" + peerDependenciesMeta: + next: + optional: true + react: + optional: true + checksum: 10c0/44a052012d5bc27c8afcc8d2c96f79d2857d2eabcf5fc57c0684c09240f67de26d814fd212cfffa1a84340bf17d8c87eb32792d2e12301c6e95aaf0bf21b7250 + languageName: node + linkType: hard + +"@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz::locator=nextjs-pages-router%40workspace%3A.": + version: 6.27.1 + resolution: "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz#../../../packages/rum-react/package.tgz::hash=d63e04&locator=nextjs-pages-router%40workspace%3A." + dependencies: + "@datadog/browser-core": "npm:6.27.1" + "@datadog/browser-rum-core": "npm:6.27.1" + peerDependencies: + react: 18 || 19 + react-router: 6 || 7 + react-router-dom: 6 || 7 + peerDependenciesMeta: + "@datadog/browser-rum": + optional: true + "@datadog/browser-rum-slim": + optional: true + react: + optional: true + react-router: + optional: true + react-router-dom: + optional: true + checksum: 10c0/53dec6173c8d968b771321cb54e95f67578fb1d65d41401110d745e79fa4fe152273ed7de3211305571bfe0adb87e5cd530f7ea5279e4df5eb03e6b4d0975d15 + languageName: node + linkType: hard + +"@datadog/browser-rum@file:../../../packages/rum/package.tgz::locator=nextjs-pages-router%40workspace%3A.": + version: 6.27.1 + resolution: "@datadog/browser-rum@file:../../../packages/rum/package.tgz#../../../packages/rum/package.tgz::hash=f49d49&locator=nextjs-pages-router%40workspace%3A." + dependencies: + "@datadog/browser-core": "npm:6.27.1" + "@datadog/browser-rum-core": "npm:6.27.1" + peerDependencies: + "@datadog/browser-logs": 6.27.1 + peerDependenciesMeta: + "@datadog/browser-logs": + optional: true + checksum: 10c0/47da488faad9c116b054b5f289f96d2f4265bd125c828db3a6e416f2ff1fa39485883d54633446fa96e2eb5d9840019bbe51eef8d346fb2572917c76560de513 + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.7.0": + version: 1.8.1 + resolution: "@emnapi/runtime@npm:1.8.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/f4929d75e37aafb24da77d2f58816761fe3f826aad2e37fa6d4421dac9060cbd5098eea1ac3c9ecc4526b89deb58153852fa432f87021dc57863f2ff726d713f + languageName: node + linkType: hard + +"@img/colour@npm:^1.0.0": + version: 1.0.0 + resolution: "@img/colour@npm:1.0.0" + checksum: 10c0/02261719c1e0d7aa5a2d585981954f2ac126f0c432400aa1a01b925aa2c41417b7695da8544ee04fd29eba7ecea8eaf9b8bef06f19dc8faba78f94eeac40667d + languageName: node + linkType: hard + +"@img/sharp-darwin-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-darwin-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-darwin-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-arm64": + optional: true + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-darwin-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-darwin-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-darwin-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-x64": + optional: true + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-arm@npm:1.2.4" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-ppc64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.4" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-riscv64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-riscv64@npm:1.2.4" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-s390x@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.4" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-x64@npm:1.2.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linux-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-arm@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-arm@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-arm": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-arm": + optional: true + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-ppc64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-ppc64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-ppc64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-ppc64": + optional: true + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-riscv64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-riscv64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-riscv64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-riscv64": + optional: true + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-s390x@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-s390x@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-s390x": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-s390x": + optional: true + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linuxmusl-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-wasm32@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-wasm32@npm:0.34.5" + dependencies: + "@emnapi/runtime": "npm:^1.7.0" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@img/sharp-win32-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-arm64@npm:0.34.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-win32-ia32@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-ia32@npm:0.34.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@img/sharp-win32-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-x64@npm:0.34.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@next/env@npm:16.1.6": + version: 16.1.6 + resolution: "@next/env@npm:16.1.6" + checksum: 10c0/ed7023edb94b9b2e5da3f9c99d08b614da9757c1edd0ecec792fce4d336b4f0c64db1a84955e07cfbd848b9e61c4118fff28f4098cd7b0a7f97814a90565ebe6 + languageName: node + linkType: hard + +"@next/swc-darwin-arm64@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-darwin-arm64@npm:16.1.6" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@next/swc-darwin-x64@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-darwin-x64@npm:16.1.6" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@next/swc-linux-arm64-gnu@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-linux-arm64-gnu@npm:16.1.6" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@next/swc-linux-arm64-musl@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-linux-arm64-musl@npm:16.1.6" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@next/swc-linux-x64-gnu@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-linux-x64-gnu@npm:16.1.6" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@next/swc-linux-x64-musl@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-linux-x64-musl@npm:16.1.6" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@next/swc-win32-arm64-msvc@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-win32-arm64-msvc@npm:16.1.6" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@next/swc-win32-x64-msvc@npm:16.1.6": + version: 16.1.6 + resolution: "@next/swc-win32-x64-msvc@npm:16.1.6" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@swc/helpers@npm:0.5.15": + version: 0.5.15 + resolution: "@swc/helpers@npm:0.5.15" + dependencies: + tslib: "npm:^2.8.0" + checksum: 10c0/33002f74f6f885f04c132960835fdfc474186983ea567606db62e86acd0680ca82f34647e8e610f4e1e422d1c16fce729dde22cd3b797ab1fd9061a825dabca4 + languageName: node + linkType: hard + +"@types/node@npm:22.16.0": + version: 22.16.0 + resolution: "@types/node@npm:22.16.0" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10c0/6219b521062f6c38d4d85ebd25807bd7f2bc703a5acba24e2c6716938d9d6cefd6fafd7b5156f61580eb58a0d82e8921751b778655675389631d813e5f261c03 + languageName: node + linkType: hard + +"@types/react-dom@npm:19.2.3": + version: 19.2.3 + resolution: "@types/react-dom@npm:19.2.3" + peerDependencies: + "@types/react": ^19.2.0 + checksum: 10c0/b486ebe0f4e2fb35e2e108df1d8fc0927ca5d6002d5771e8a739de11239fe62d0e207c50886185253c99eb9dedfeeb956ea7429e5ba17f6693c7acb4c02f8cd1 + languageName: node + linkType: hard + +"@types/react@npm:19.2.8": + version: 19.2.8 + resolution: "@types/react@npm:19.2.8" + dependencies: + csstype: "npm:^3.2.2" + checksum: 10c0/832834998c4ee971fca72ecf1eb95dc924ad3931a2112c687a4dae498aabd115c5fa4db09186853e34a646226b0223808c8f867df03d17601168f9cf119448de + languageName: node + linkType: hard + +"baseline-browser-mapping@npm:^2.8.3": + version: 2.10.0 + resolution: "baseline-browser-mapping@npm:2.10.0" + bin: + baseline-browser-mapping: dist/cli.cjs + checksum: 10c0/da9c3ec0fcd7f325226a47d2142794d41706b6e0a405718a2c15410bbdb72aacadd65738bedef558c6f1b106ed19458cb25b06f63b66df2c284799905dbbd003 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001579": + version: 1.0.30001770 + resolution: "caniuse-lite@npm:1.0.30001770" + checksum: 10c0/02d15a8b723af65318cb4d888a52bb090076898da7b0de99e8676d537f8d1d2ae4797e81518e1e30cbfe84c33b048c322e8bfafc5b23cfee8defb0d2bf271149 + languageName: node + linkType: hard + +"client-only@npm:0.0.1": + version: 0.0.1 + resolution: "client-only@npm:0.0.1" + checksum: 10c0/9d6cfd0c19e1c96a434605added99dff48482152af791ec4172fb912a71cff9027ff174efd8cdb2160cc7f377543e0537ffc462d4f279bc4701de3f2a3c4b358 + languageName: node + linkType: hard + +"csstype@npm:^3.2.2": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce + languageName: node + linkType: hard + +"detect-libc@npm:^2.1.2": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.6": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + +"next@npm:16.1.6": + version: 16.1.6 + resolution: "next@npm:16.1.6" + dependencies: + "@next/env": "npm:16.1.6" + "@next/swc-darwin-arm64": "npm:16.1.6" + "@next/swc-darwin-x64": "npm:16.1.6" + "@next/swc-linux-arm64-gnu": "npm:16.1.6" + "@next/swc-linux-arm64-musl": "npm:16.1.6" + "@next/swc-linux-x64-gnu": "npm:16.1.6" + "@next/swc-linux-x64-musl": "npm:16.1.6" + "@next/swc-win32-arm64-msvc": "npm:16.1.6" + "@next/swc-win32-x64-msvc": "npm:16.1.6" + "@swc/helpers": "npm:0.5.15" + baseline-browser-mapping: "npm:^2.8.3" + caniuse-lite: "npm:^1.0.30001579" + postcss: "npm:8.4.31" + sharp: "npm:^0.34.4" + styled-jsx: "npm:5.1.6" + peerDependencies: + "@opentelemetry/api": ^1.1.0 + "@playwright/test": ^1.51.1 + babel-plugin-react-compiler: "*" + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + dependenciesMeta: + "@next/swc-darwin-arm64": + optional: true + "@next/swc-darwin-x64": + optional: true + "@next/swc-linux-arm64-gnu": + optional: true + "@next/swc-linux-arm64-musl": + optional: true + "@next/swc-linux-x64-gnu": + optional: true + "@next/swc-linux-x64-musl": + optional: true + "@next/swc-win32-arm64-msvc": + optional: true + "@next/swc-win32-x64-msvc": + optional: true + sharp: + optional: true + peerDependenciesMeta: + "@opentelemetry/api": + optional: true + "@playwright/test": + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + bin: + next: dist/bin/next + checksum: 10c0/543766bf879bb5a5d454dc18cb302953270a92efba1d01dd028ea83c64b69573ce7d6e6c3759ecbaabec0a84131b0237263c24d1ccd7c8a97205e776dcd34e0b + languageName: node + linkType: hard + +"nextjs-pages-router@workspace:.": + version: 0.0.0-use.local + resolution: "nextjs-pages-router@workspace:." + dependencies: + "@datadog/browser-rum": "file:../../../packages/rum/package.tgz" + "@datadog/browser-rum-nextjs": "file:../../../packages/rum-nextjs/package.tgz" + "@datadog/browser-rum-react": "file:../../../packages/rum-react/package.tgz" + "@types/node": "npm:22.16.0" + "@types/react": "npm:19.2.8" + "@types/react-dom": "npm:19.2.3" + next: "npm:16.1.6" + react: "npm:19.2.3" + react-dom: "npm:19.2.3" + typescript: "npm:5.9.3" + languageName: unknown + linkType: soft + +"picocolors@npm:^1.0.0": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"postcss@npm:8.4.31": + version: 8.4.31 + resolution: "postcss@npm:8.4.31" + dependencies: + nanoid: "npm:^3.3.6" + picocolors: "npm:^1.0.0" + source-map-js: "npm:^1.0.2" + checksum: 10c0/748b82e6e5fc34034dcf2ae88ea3d11fd09f69b6c50ecdd3b4a875cfc7cdca435c958b211e2cb52355422ab6fccb7d8f2f2923161d7a1b281029e4a913d59acf + languageName: node + linkType: hard + +"react-dom@npm:19.2.3": + version: 19.2.3 + resolution: "react-dom@npm:19.2.3" + dependencies: + scheduler: "npm:^0.27.0" + peerDependencies: + react: ^19.2.3 + checksum: 10c0/dc43f7ede06f46f3acc16ee83107c925530de9b91d1d0b3824583814746ff4c498ea64fd65cd83aba363205268adff52e2827c582634ae7b15069deaeabc4892 + languageName: node + linkType: hard + +"react@npm:19.2.3": + version: 19.2.3 + resolution: "react@npm:19.2.3" + checksum: 10c0/094220b3ba3a76c1b668f972ace1dd15509b157aead1b40391d1c8e657e720c201d9719537375eff08f5e0514748c0319063392a6f000e31303aafc4471f1436 + languageName: node + linkType: hard + +"scheduler@npm:^0.27.0": + version: 0.27.0 + resolution: "scheduler@npm:0.27.0" + checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452 + languageName: node + linkType: hard + +"semver@npm:^7.7.3": + version: 7.7.4 + resolution: "semver@npm:7.7.4" + bin: + semver: bin/semver.js + checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 + languageName: node + linkType: hard + +"sharp@npm:^0.34.4": + version: 0.34.5 + resolution: "sharp@npm:0.34.5" + dependencies: + "@img/colour": "npm:^1.0.0" + "@img/sharp-darwin-arm64": "npm:0.34.5" + "@img/sharp-darwin-x64": "npm:0.34.5" + "@img/sharp-libvips-darwin-arm64": "npm:1.2.4" + "@img/sharp-libvips-darwin-x64": "npm:1.2.4" + "@img/sharp-libvips-linux-arm": "npm:1.2.4" + "@img/sharp-libvips-linux-arm64": "npm:1.2.4" + "@img/sharp-libvips-linux-ppc64": "npm:1.2.4" + "@img/sharp-libvips-linux-riscv64": "npm:1.2.4" + "@img/sharp-libvips-linux-s390x": "npm:1.2.4" + "@img/sharp-libvips-linux-x64": "npm:1.2.4" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4" + "@img/sharp-linux-arm": "npm:0.34.5" + "@img/sharp-linux-arm64": "npm:0.34.5" + "@img/sharp-linux-ppc64": "npm:0.34.5" + "@img/sharp-linux-riscv64": "npm:0.34.5" + "@img/sharp-linux-s390x": "npm:0.34.5" + "@img/sharp-linux-x64": "npm:0.34.5" + "@img/sharp-linuxmusl-arm64": "npm:0.34.5" + "@img/sharp-linuxmusl-x64": "npm:0.34.5" + "@img/sharp-wasm32": "npm:0.34.5" + "@img/sharp-win32-arm64": "npm:0.34.5" + "@img/sharp-win32-ia32": "npm:0.34.5" + "@img/sharp-win32-x64": "npm:0.34.5" + detect-libc: "npm:^2.1.2" + semver: "npm:^7.7.3" + dependenciesMeta: + "@img/sharp-darwin-arm64": + optional: true + "@img/sharp-darwin-x64": + optional: true + "@img/sharp-libvips-darwin-arm64": + optional: true + "@img/sharp-libvips-darwin-x64": + optional: true + "@img/sharp-libvips-linux-arm": + optional: true + "@img/sharp-libvips-linux-arm64": + optional: true + "@img/sharp-libvips-linux-ppc64": + optional: true + "@img/sharp-libvips-linux-riscv64": + optional: true + "@img/sharp-libvips-linux-s390x": + optional: true + "@img/sharp-libvips-linux-x64": + optional: true + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + "@img/sharp-libvips-linuxmusl-x64": + optional: true + "@img/sharp-linux-arm": + optional: true + "@img/sharp-linux-arm64": + optional: true + "@img/sharp-linux-ppc64": + optional: true + "@img/sharp-linux-riscv64": + optional: true + "@img/sharp-linux-s390x": + optional: true + "@img/sharp-linux-x64": + optional: true + "@img/sharp-linuxmusl-arm64": + optional: true + "@img/sharp-linuxmusl-x64": + optional: true + "@img/sharp-wasm32": + optional: true + "@img/sharp-win32-arm64": + optional: true + "@img/sharp-win32-ia32": + optional: true + "@img/sharp-win32-x64": + optional: true + checksum: 10c0/fd79e29df0597a7d5704b8461c51f944ead91a5243691697be6e8243b966402beda53ddc6f0a53b96ea3cb8221f0b244aa588114d3ebf8734fb4aefd41ab802f + languageName: node + linkType: hard + +"source-map-js@npm:^1.0.2": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + +"styled-jsx@npm:5.1.6": + version: 5.1.6 + resolution: "styled-jsx@npm:5.1.6" + dependencies: + client-only: "npm:0.0.1" + peerDependencies: + react: ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + peerDependenciesMeta: + "@babel/core": + optional: true + babel-plugin-macros: + optional: true + checksum: 10c0/ace50e7ea5ae5ae6a3b65a50994c51fca6ae7df9c7ecfd0104c36be0b4b3a9c5c1a2374d16e2a11e256d0b20be6d47256d768ecb4f91ab390f60752a075780f5 + languageName: node + linkType: hard + +"tslib@npm:^2.4.0, tslib@npm:^2.8.0": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + +"typescript@npm:5.9.3": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A5.9.3#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + languageName: node + linkType: hard + +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10c0/c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04 + languageName: node + linkType: hard diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 56ead6f1f0..7265f17e80 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -9,6 +9,8 @@ import { BrowserLogsManager, deleteAllCookies, getBrowserName, sendXhr } from '. import { DEFAULT_LOGS_CONFIGURATION, DEFAULT_RUM_CONFIGURATION } from '../helpers/configuration' import { validateRumFormat } from '../helpers/validation' import type { BrowserConfiguration } from '../../../browsers.conf' +import type { NextjsRouter } from '../helpers/playwright' +import { getNextjsUrl } from '../helpers/playwright' import { IntakeRegistry } from './intakeRegistry' import { flushEvents } from './flushEvents' import type { Servers } from './httpServers' @@ -39,6 +41,7 @@ interface TestContext { deleteAllCookies: () => Promise sendXhr: (url: string, headers?: string[][]) => Promise evaluateInWorker: (fn: () => void) => Promise + isNextjsApp: boolean } type TestRunner = (testContext: TestContext) => Promise | void @@ -60,6 +63,7 @@ class TestBuilder { } = {} private worker: Worker | undefined private hostName?: string + private nextjsRouter?: NextjsRouter constructor( private title: string, @@ -111,6 +115,12 @@ class TestBuilder { return this } + withNextjsApp(router: NextjsRouter) { + this.nextjsRouter = router + this.setups = [{ factory: () => '' }] + return this + } + withBasePath(newBasePath: string) { this.basePath = newBasePath return this @@ -179,6 +189,7 @@ class TestBuilder { testFixture: this.testFixture, extension: this.extension, hostName: this.hostName, + nextjsRouter: this.nextjsRouter, worker: this.worker, callerLocation: this.callerLocation, } @@ -302,17 +313,21 @@ function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFa const testContext = createTestContext(servers, page, context, browserLogs, browserName, setupOptions) servers.intake.bindServerApp(createIntakeServerApp(testContext.intakeRegistry)) - const setup = factory(setupOptions, servers) - servers.base.bindServerApp( - createMockServerApp(servers, setup, setupOptions.remoteConfiguration, setupOptions.worker) - ) - servers.crossOrigin.bindServerApp(createMockServerApp(servers, setup)) + if (!setupOptions.nextjsRouter) { + const setup = factory(setupOptions, servers) + servers.base.bindServerApp( + createMockServerApp(servers, setup, setupOptions.remoteConfiguration, setupOptions.worker) + ) + servers.crossOrigin.bindServerApp(createMockServerApp(servers, setup)) + } - await setUpTest(browserLogs, testContext) + await setUpTest(browserLogs, testContext, setupOptions, servers) try { await runner(testContext) - tearDownPassedTest(testContext) + if (!setupOptions.nextjsRouter) { + tearDownPassedTest(testContext) + } } finally { await tearDownTest(testContext) } @@ -325,21 +340,28 @@ function createTestContext( browserContext: BrowserContext, browserLogsManager: BrowserLogsManager, browserName: TestContext['browserName'], - { basePath, hostName }: SetupOptions + { basePath, hostName, nextjsRouter }: SetupOptions ): TestContext { - const baseUrl = new URL(basePath, servers.base.origin) + let baseUrl: string - if (hostName) { - baseUrl.hostname = hostName + if (nextjsRouter) { + baseUrl = getNextjsUrl(nextjsRouter) + } else { + const url = new URL(basePath, servers.base.origin) + if (hostName) { + url.hostname = hostName + } + baseUrl = url.href } return { - baseUrl: baseUrl.href, + baseUrl, intakeRegistry: new IntakeRegistry(), servers, page, browserContext, browserName, + isNextjsApp: !!nextjsRouter, withBrowserLogs: (cb: (logs: BrowserLog[]) => void) => { try { cb(browserLogsManager.get()) @@ -365,7 +387,7 @@ function createTestContext( }, `(${fn.toString()})()`) }, flushBrowserLogs: () => browserLogsManager.clear(), - flushEvents: () => flushEvents(page), + flushEvents: () => flushEvents(page, nextjsRouter ? getNextjsUrl(nextjsRouter) : undefined), deleteAllCookies: () => deleteAllCookies(browserContext), sendXhr: (url: string, headers?: string[][]) => sendXhr(page, url, headers), getExtensionId: async () => { @@ -380,7 +402,12 @@ function createTestContext( } } -async function setUpTest(browserLogsManager: BrowserLogsManager, { baseUrl, page, browserContext }: TestContext) { +async function setUpTest( + browserLogsManager: BrowserLogsManager, + { baseUrl, page, browserContext }: TestContext, + setupOptions: SetupOptions, + servers: Servers +) { browserContext.on('console', (msg) => { browserLogsManager.add({ level: msg.type() as BrowserLog['level'], @@ -399,6 +426,15 @@ async function setUpTest(browserLogsManager: BrowserLogsManager, { baseUrl, page }) }) + if (setupOptions.nextjsRouter && setupOptions.rum) { + await page.addInitScript( + ({ config }) => { + ;(window as any).RUM_CONFIGURATION = config + }, + { config: { ...setupOptions.rum, proxy: servers.intake.origin } } + ) + } + await page.goto(baseUrl) await waitForServersIdle() } diff --git a/test/e2e/lib/framework/flushEvents.ts b/test/e2e/lib/framework/flushEvents.ts index 7567d42116..1a30c26e4c 100644 --- a/test/e2e/lib/framework/flushEvents.ts +++ b/test/e2e/lib/framework/flushEvents.ts @@ -2,7 +2,7 @@ import type { Page } from '@playwright/test' import { getTestServers, waitForServersIdle } from './httpServers' import { waitForRequests } from './waitForRequests' -export async function flushEvents(page: Page) { +export async function flushEvents(page: Page, gotoUrl?: string) { await waitForRequests(page) const servers = await getTestServers() @@ -21,6 +21,6 @@ export async function flushEvents(page: Page) { // The issue mainly occurs with local e2e tests (not browserstack), because the network latency is // very low (same machine), so the request resolves very quickly. In real life conditions, this // issue is mitigated, because requests will likely take a few milliseconds to reach the server. - await page.goto(`${servers.base.origin}/ok?duration=200`) + await page.goto(gotoUrl ?? `${servers.base.origin}/ok?duration=200`) await waitForServersIdle() } diff --git a/test/e2e/lib/framework/pageSetups.ts b/test/e2e/lib/framework/pageSetups.ts index 22ec67f9b8..c00c8e647e 100644 --- a/test/e2e/lib/framework/pageSetups.ts +++ b/test/e2e/lib/framework/pageSetups.ts @@ -2,6 +2,7 @@ import { generateUUID, INTAKE_URL_PARAMETERS } from '@datadog/browser-core' import type { LogsInitConfiguration } from '@datadog/browser-logs' import type { RumInitConfiguration, RemoteConfiguration } from '@datadog/browser-rum-core' import type test from '@playwright/test' +import type { NextjsRouter } from '../helpers/playwright' import { isBrowserStack, isContinuousIntegration } from './environment' import type { Servers } from './httpServers' @@ -26,6 +27,7 @@ export interface SetupOptions { logsConfiguration?: LogsInitConfiguration } hostName?: string + nextjsRouter?: NextjsRouter worker?: WorkerOptions callerLocation?: CallerLocation } diff --git a/test/e2e/lib/helpers/playwright.ts b/test/e2e/lib/helpers/playwright.ts index a6affa5973..bf0ccb3308 100644 --- a/test/e2e/lib/helpers/playwright.ts +++ b/test/e2e/lib/helpers/playwright.ts @@ -5,6 +5,18 @@ import packageJson from '../../../../package.json' with { type: 'json' } export const DEV_SERVER_BASE_URL = 'http://localhost:8080' +export const enum NextjsRouter { + App = 'app', + Pages = 'pages', +} + +export const NEXTJS_APP_URL = 'http://localhost:3000' +export const NEXTJS_PAGES_URL = 'http://localhost:3001' + +export function getNextjsUrl(router: NextjsRouter): string { + return router === NextjsRouter.App ? NEXTJS_APP_URL : NEXTJS_PAGES_URL +} + export function getPlaywrightConfigBrowserName(name: string): PlaywrightWorkerOptions['browserName'] { if (name.includes('firefox')) { return 'firefox' diff --git a/test/e2e/playwright.base.config.ts b/test/e2e/playwright.base.config.ts index 9bc8fb1a0d..f7d0965e93 100644 --- a/test/e2e/playwright.base.config.ts +++ b/test/e2e/playwright.base.config.ts @@ -1,7 +1,7 @@ import path from 'path' import type { ReporterDescription, Config } from '@playwright/test' import { getTestReportDirectory } from '../envUtils' -import { DEV_SERVER_BASE_URL } from './lib/helpers/playwright' +import { DEV_SERVER_BASE_URL, NEXTJS_APP_URL, NEXTJS_PAGES_URL } from './lib/helpers/playwright' const isCi = !!process.env.CI const isLocal = !isCi @@ -31,13 +31,31 @@ export const config: Config = { trace: isCi ? 'off' : 'retain-on-failure', }, - webServer: isLocal - ? { - stdout: 'pipe', - cwd: path.join(__dirname, '../..'), - command: 'yarn dev', - url: DEV_SERVER_BASE_URL, - reuseExistingServer: true, - } - : undefined, + webServer: [ + ...(isLocal + ? [ + { + stdout: 'pipe' as const, + cwd: path.join(__dirname, '../..'), + command: 'yarn dev', + url: DEV_SERVER_BASE_URL, + reuseExistingServer: true, + }, + ] + : []), + { + stdout: 'pipe' as const, + cwd: path.join(__dirname, '../apps/nextjs-app-router'), + command: isLocal ? 'yarn dev' : 'yarn start', + url: NEXTJS_APP_URL, + reuseExistingServer: true, + }, + { + stdout: 'pipe' as const, + cwd: path.join(__dirname, '../apps/nextjs-pages-router'), + command: isLocal ? 'yarn dev' : 'yarn start', + url: NEXTJS_PAGES_URL, + reuseExistingServer: true, + }, + ], } diff --git a/test/e2e/scenario/nextjsPlugin.scenario.ts b/test/e2e/scenario/nextjsPlugin.scenario.ts new file mode 100644 index 0000000000..803b82975e --- /dev/null +++ b/test/e2e/scenario/nextjsPlugin.scenario.ts @@ -0,0 +1,129 @@ +import { test, expect } from '@playwright/test' +import { createTest } from '../lib/framework' +import { NextjsRouter } from '../lib/helpers/playwright' + +const routers = [ + { router: NextjsRouter.App, label: 'app router', dynamicViewName: '/user/[id]', homeUrl: '**/localhost:3000/' }, + { router: NextjsRouter.Pages, label: 'pages router', dynamicViewName: '/user/[id]', homeUrl: '**/localhost:3001/' }, +] + +for (const { router, label, dynamicViewName, homeUrl } of routers) { + test.describe(`nextjs ${label}`, () => { + createTest('should track initial view') + .withRum() + .withNextjsApp(router) + .run(async ({ page, flushEvents, intakeRegistry }) => { + await page.click('text=Go to User 42') + await page.waitForURL('**/user/42') + + await flushEvents() + + const viewEvents = intakeRegistry.rumViewEvents + const homeView = viewEvents.find((e) => e.view.name === '/' && e.view.loading_type === 'initial_load') + expect(homeView).toBeDefined() + }) + + createTest(`should normalize dynamic route to ${dynamicViewName}`) + .withRum() + .withNextjsApp(router) + .run(async ({ page, flushEvents, intakeRegistry }) => { + await page.click('text=Go to User 42') + await page.waitForURL('**/user/42') + + await page.click('text=Back to Home') + await page.waitForURL(homeUrl) + + await flushEvents() + + const viewEvents = intakeRegistry.rumViewEvents + expect(viewEvents.length).toBeGreaterThanOrEqual(2) + + const homeView = viewEvents.find((e) => e.view.name === '/') + expect(homeView).toBeDefined() + + const userView = viewEvents.find((e) => e.view.name === dynamicViewName) + expect(userView).toBeDefined() + expect(userView?.view.loading_type).toBe('route_change') + }) + + createTest('should track SPA navigation with loading_time') + .withRum() + .withNextjsApp(router) + .run(async ({ page, flushEvents, intakeRegistry }) => { + await page.click('text=Go to User 42') + await page.waitForURL('**/user/42') + + await flushEvents() + + const viewEvents = intakeRegistry.rumViewEvents + const userView = viewEvents.find( + (e) => e.view.name === dynamicViewName && e.view.loading_type === 'route_change' + ) + expect(userView).toBeDefined() + expect(userView?.view.loading_time).toBeDefined() + expect(userView?.view.loading_time).toBeGreaterThan(0) + }) + + createTest('should track back navigation via popstate') + .withRum() + .withNextjsApp(router) + .run(async ({ page, flushEvents, intakeRegistry }) => { + await page.click('text=Go to User 42') + await page.waitForURL('**/user/42') + + await page.goBack() + await page.waitForURL(homeUrl) + + await flushEvents() + + const viewEvents = intakeRegistry.rumViewEvents + expect(viewEvents.filter((e) => e.view.name === '/').length).toBeGreaterThanOrEqual(2) + }) + + createTest('should send a react component render vital event') + .withRum() + .withNextjsApp(router) + .run(async ({ page, flushEvents, intakeRegistry }) => { + await page.click('text=Go to Tracked Component') + await page.waitForURL('**/tracked') + + await page.click('text=Back to Home') + await page.waitForURL(homeUrl) + + await flushEvents() + + const vitalEvents = intakeRegistry.rumVitalEvents + expect(vitalEvents.length).toBeGreaterThan(0) + + const trackedVital = vitalEvents.find((e) => e.vital.description === 'TrackedPage') + expect(trackedVital).toBeDefined() + expect(trackedVital?.vital.duration).toEqual(expect.any(Number)) + }) + }) +} + +test.describe('nextjs app router errors', () => { + createTest('should capture react error from error boundary') + .withRum() + .withNextjsApp(NextjsRouter.App) + .run(async ({ page, flushEvents, intakeRegistry, withBrowserLogs }) => { + await page.click('text=Go to Error Test') + await page.waitForURL('**/error-test') + await page.click('#error-button') + await page.waitForSelector('#error-message') + + await flushEvents() + + const errorEvents = intakeRegistry.rumErrorEvents + expect(errorEvents.length).toBeGreaterThan(0) + + const boundaryError = errorEvents.find((e) => e.error.message?.includes('Error triggered by button')) + expect(boundaryError).toBeDefined() + expect(boundaryError?.error.source).toBe('source') + expect(boundaryError?.context?.framework).toBe('nextjs') + + withBrowserLogs((browserLogs) => { + expect(browserLogs.length).toBeGreaterThan(0) + }) + }) +}) diff --git a/tsconfig.base.json b/tsconfig.base.json index c40d33dd99..82ee2aa854 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -35,6 +35,10 @@ "@datadog/browser-rum-react/react-router-v6": ["./packages/rum-react/src/entries/reactRouterV6"], "@datadog/browser-rum-react/react-router-v7": ["./packages/rum-react/src/entries/reactRouterV7"], + "@datadog/browser-rum-nextjs": ["./packages/rum-nextjs/src/entries/main"], + "@datadog/browser-rum-nextjs/app-router": ["./packages/rum-nextjs/src/entries/appRouter"], + "@datadog/browser-rum-nextjs/pages-router": ["./packages/rum-nextjs/src/entries/pagesRouter"], + "@datadog/browser-worker": ["./packages/worker/src/entries/main"] } } diff --git a/yarn.lock b/yarn.lock index 6c5aa88a58..64c15e518a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -322,6 +322,26 @@ __metadata: languageName: unknown linkType: soft +"@datadog/browser-rum-nextjs@workspace:packages/rum-nextjs": + version: 0.0.0-use.local + resolution: "@datadog/browser-rum-nextjs@workspace:packages/rum-nextjs" + dependencies: + "@datadog/browser-core": "npm:6.27.1" + "@datadog/browser-rum-core": "npm:6.27.1" + "@types/react": "npm:19.2.11" + next: "npm:15.3.3" + react: "npm:19.2.4" + peerDependencies: + next: ">=12.0.0" + react: ">=18.0.0" + peerDependenciesMeta: + next: + optional: true + react: + optional: true + languageName: unknown + linkType: soft + "@datadog/browser-rum-react@workspace:packages/rum-react": version: 0.0.0-use.local resolution: "@datadog/browser-rum-react@workspace:packages/rum-react" @@ -462,7 +482,7 @@ __metadata: languageName: node linkType: hard -"@emnapi/runtime@npm:^1.1.0, @emnapi/runtime@npm:^1.4.3": +"@emnapi/runtime@npm:^1.1.0, @emnapi/runtime@npm:^1.4.3, @emnapi/runtime@npm:^1.7.0": version: 1.8.1 resolution: "@emnapi/runtime@npm:1.8.1" dependencies: @@ -873,6 +893,233 @@ __metadata: languageName: node linkType: hard +"@img/colour@npm:^1.0.0": + version: 1.0.0 + resolution: "@img/colour@npm:1.0.0" + checksum: 10c0/02261719c1e0d7aa5a2d585981954f2ac126f0c432400aa1a01b925aa2c41417b7695da8544ee04fd29eba7ecea8eaf9b8bef06f19dc8faba78f94eeac40667d + languageName: node + linkType: hard + +"@img/sharp-darwin-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-darwin-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-darwin-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-arm64": + optional: true + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-darwin-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-darwin-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-darwin-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-x64": + optional: true + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-arm@npm:1.2.4" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-ppc64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.4" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-riscv64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-riscv64@npm:1.2.4" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-s390x@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.4" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-x64@npm:1.2.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linux-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-arm@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-arm@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-arm": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-arm": + optional: true + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-ppc64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-ppc64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-ppc64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-ppc64": + optional: true + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-riscv64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-riscv64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-riscv64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-riscv64": + optional: true + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-s390x@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-s390x@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-s390x": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-s390x": + optional: true + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linuxmusl-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-wasm32@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-wasm32@npm:0.34.5" + dependencies: + "@emnapi/runtime": "npm:^1.7.0" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@img/sharp-win32-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-arm64@npm:0.34.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-win32-ia32@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-ia32@npm:0.34.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@img/sharp-win32-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-x64@npm:0.34.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@inquirer/ansi@npm:^1.0.0, @inquirer/ansi@npm:^1.0.2": version: 1.0.2 resolution: "@inquirer/ansi@npm:1.0.2" @@ -1634,6 +1881,69 @@ __metadata: languageName: node linkType: hard +"@next/env@npm:15.3.3": + version: 15.3.3 + resolution: "@next/env@npm:15.3.3" + checksum: 10c0/b47ef78c4194900f52a274270932a633ba21f39377fc6ad478839c3c1e3fffccb8ad25b286a1beb11f91fe9d09a299087ccb9c205a4e610ad95af65f24e49e5a + languageName: node + linkType: hard + +"@next/swc-darwin-arm64@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-darwin-arm64@npm:15.3.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@next/swc-darwin-x64@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-darwin-x64@npm:15.3.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@next/swc-linux-arm64-gnu@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-linux-arm64-gnu@npm:15.3.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@next/swc-linux-arm64-musl@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-linux-arm64-musl@npm:15.3.3" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@next/swc-linux-x64-gnu@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-linux-x64-gnu@npm:15.3.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@next/swc-linux-x64-musl@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-linux-x64-musl@npm:15.3.3" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@next/swc-win32-arm64-msvc@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-win32-arm64-msvc@npm:15.3.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@next/swc-win32-x64-msvc@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-win32-x64-msvc@npm:15.3.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -2673,13 +2983,22 @@ __metadata: languageName: node linkType: hard -"@swc/counter@npm:^0.1.3": +"@swc/counter@npm:0.1.3, @swc/counter@npm:^0.1.3": version: 0.1.3 resolution: "@swc/counter@npm:0.1.3" checksum: 10c0/8424f60f6bf8694cfd2a9bca45845bce29f26105cda8cf19cdb9fd3e78dc6338699e4db77a89ae449260bafa1cc6bec307e81e7fb96dbf7dcfce0eea55151356 languageName: node linkType: hard +"@swc/helpers@npm:0.5.15": + version: 0.5.15 + resolution: "@swc/helpers@npm:0.5.15" + dependencies: + tslib: "npm:^2.8.0" + checksum: 10c0/33002f74f6f885f04c132960835fdfc474186983ea567606db62e86acd0680ca82f34647e8e610f4e1e422d1c16fce729dde22cd3b797ab1fd9061a825dabca4 + languageName: node + linkType: hard + "@swc/types@npm:^0.1.25": version: 0.1.25 resolution: "@swc/types@npm:0.1.25" @@ -4589,7 +4908,7 @@ __metadata: languageName: node linkType: hard -"busboy@npm:^1.0.0": +"busboy@npm:1.6.0, busboy@npm:^1.0.0": version: 1.6.0 resolution: "busboy@npm:1.6.0" dependencies: @@ -4757,6 +5076,13 @@ __metadata: languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001579": + version: 1.0.30001770 + resolution: "caniuse-lite@npm:1.0.30001770" + checksum: 10c0/02d15a8b723af65318cb4d888a52bb090076898da7b0de99e8676d537f8d1d2ae4797e81518e1e30cbfe84c33b048c322e8bfafc5b23cfee8defb0d2bf271149 + languageName: node + linkType: hard + "caniuse-lite@npm:^1.0.30001759": version: 1.0.30001768 resolution: "caniuse-lite@npm:1.0.30001768" @@ -5010,6 +5336,13 @@ __metadata: languageName: node linkType: hard +"client-only@npm:0.0.1": + version: 0.0.1 + resolution: "client-only@npm:0.0.1" + checksum: 10c0/9d6cfd0c19e1c96a434605added99dff48482152af791ec4172fb912a71cff9027ff174efd8cdb2160cc7f377543e0537ffc462d4f279bc4701de3f2a3c4b358 + languageName: node + linkType: hard + "cliui@npm:^7.0.2": version: 7.0.4 resolution: "cliui@npm:7.0.4" @@ -5840,6 +6173,13 @@ __metadata: languageName: node linkType: hard +"detect-libc@npm:^2.1.2": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 + languageName: node + linkType: hard + "detect-node-es@npm:^1.1.0": version: 1.1.0 resolution: "detect-node-es@npm:1.1.0" @@ -10628,7 +10968,7 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.11": +"nanoid@npm:^3.3.11, nanoid@npm:^3.3.6": version: 3.3.11 resolution: "nanoid@npm:3.3.11" bin: @@ -10681,6 +11021,67 @@ __metadata: languageName: node linkType: hard +"next@npm:15.3.3": + version: 15.3.3 + resolution: "next@npm:15.3.3" + dependencies: + "@next/env": "npm:15.3.3" + "@next/swc-darwin-arm64": "npm:15.3.3" + "@next/swc-darwin-x64": "npm:15.3.3" + "@next/swc-linux-arm64-gnu": "npm:15.3.3" + "@next/swc-linux-arm64-musl": "npm:15.3.3" + "@next/swc-linux-x64-gnu": "npm:15.3.3" + "@next/swc-linux-x64-musl": "npm:15.3.3" + "@next/swc-win32-arm64-msvc": "npm:15.3.3" + "@next/swc-win32-x64-msvc": "npm:15.3.3" + "@swc/counter": "npm:0.1.3" + "@swc/helpers": "npm:0.5.15" + busboy: "npm:1.6.0" + caniuse-lite: "npm:^1.0.30001579" + postcss: "npm:8.4.31" + sharp: "npm:^0.34.1" + styled-jsx: "npm:5.1.6" + peerDependencies: + "@opentelemetry/api": ^1.1.0 + "@playwright/test": ^1.41.2 + babel-plugin-react-compiler: "*" + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + dependenciesMeta: + "@next/swc-darwin-arm64": + optional: true + "@next/swc-darwin-x64": + optional: true + "@next/swc-linux-arm64-gnu": + optional: true + "@next/swc-linux-arm64-musl": + optional: true + "@next/swc-linux-x64-gnu": + optional: true + "@next/swc-linux-x64-musl": + optional: true + "@next/swc-win32-arm64-msvc": + optional: true + "@next/swc-win32-x64-msvc": + optional: true + sharp: + optional: true + peerDependenciesMeta: + "@opentelemetry/api": + optional: true + "@playwright/test": + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + bin: + next: dist/bin/next + checksum: 10c0/b519d348efd905ac63b2e5cb1e5a3d8e5d11d992aba436f4eef28c66f4555f155bb2bd489d0d029867e926539b31a3f14dd81b0ebca54ce9f3d65a883fb94d4b + languageName: node + linkType: hard + "no-case@npm:^3.0.4": version: 3.0.4 resolution: "no-case@npm:3.0.4" @@ -11815,7 +12216,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.1.1": +"picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 @@ -11990,6 +12391,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:8.4.31": + version: 8.4.31 + resolution: "postcss@npm:8.4.31" + dependencies: + nanoid: "npm:^3.3.6" + picocolors: "npm:^1.0.0" + source-map-js: "npm:^1.0.2" + checksum: 10c0/748b82e6e5fc34034dcf2ae88ea3d11fd09f69b6c50ecdd3b4a875cfc7cdca435c958b211e2cb52355422ab6fccb7d8f2f2923161d7a1b281029e4a913d59acf + languageName: node + linkType: hard + "postcss@npm:^8.5.6": version: 8.5.6 resolution: "postcss@npm:8.5.6" @@ -13366,6 +13778,90 @@ __metadata: languageName: node linkType: hard +"sharp@npm:^0.34.1": + version: 0.34.5 + resolution: "sharp@npm:0.34.5" + dependencies: + "@img/colour": "npm:^1.0.0" + "@img/sharp-darwin-arm64": "npm:0.34.5" + "@img/sharp-darwin-x64": "npm:0.34.5" + "@img/sharp-libvips-darwin-arm64": "npm:1.2.4" + "@img/sharp-libvips-darwin-x64": "npm:1.2.4" + "@img/sharp-libvips-linux-arm": "npm:1.2.4" + "@img/sharp-libvips-linux-arm64": "npm:1.2.4" + "@img/sharp-libvips-linux-ppc64": "npm:1.2.4" + "@img/sharp-libvips-linux-riscv64": "npm:1.2.4" + "@img/sharp-libvips-linux-s390x": "npm:1.2.4" + "@img/sharp-libvips-linux-x64": "npm:1.2.4" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4" + "@img/sharp-linux-arm": "npm:0.34.5" + "@img/sharp-linux-arm64": "npm:0.34.5" + "@img/sharp-linux-ppc64": "npm:0.34.5" + "@img/sharp-linux-riscv64": "npm:0.34.5" + "@img/sharp-linux-s390x": "npm:0.34.5" + "@img/sharp-linux-x64": "npm:0.34.5" + "@img/sharp-linuxmusl-arm64": "npm:0.34.5" + "@img/sharp-linuxmusl-x64": "npm:0.34.5" + "@img/sharp-wasm32": "npm:0.34.5" + "@img/sharp-win32-arm64": "npm:0.34.5" + "@img/sharp-win32-ia32": "npm:0.34.5" + "@img/sharp-win32-x64": "npm:0.34.5" + detect-libc: "npm:^2.1.2" + semver: "npm:^7.7.3" + dependenciesMeta: + "@img/sharp-darwin-arm64": + optional: true + "@img/sharp-darwin-x64": + optional: true + "@img/sharp-libvips-darwin-arm64": + optional: true + "@img/sharp-libvips-darwin-x64": + optional: true + "@img/sharp-libvips-linux-arm": + optional: true + "@img/sharp-libvips-linux-arm64": + optional: true + "@img/sharp-libvips-linux-ppc64": + optional: true + "@img/sharp-libvips-linux-riscv64": + optional: true + "@img/sharp-libvips-linux-s390x": + optional: true + "@img/sharp-libvips-linux-x64": + optional: true + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + "@img/sharp-libvips-linuxmusl-x64": + optional: true + "@img/sharp-linux-arm": + optional: true + "@img/sharp-linux-arm64": + optional: true + "@img/sharp-linux-ppc64": + optional: true + "@img/sharp-linux-riscv64": + optional: true + "@img/sharp-linux-s390x": + optional: true + "@img/sharp-linux-x64": + optional: true + "@img/sharp-linuxmusl-arm64": + optional: true + "@img/sharp-linuxmusl-x64": + optional: true + "@img/sharp-wasm32": + optional: true + "@img/sharp-win32-arm64": + optional: true + "@img/sharp-win32-ia32": + optional: true + "@img/sharp-win32-x64": + optional: true + checksum: 10c0/fd79e29df0597a7d5704b8461c51f944ead91a5243691697be6e8243b966402beda53ddc6f0a53b96ea3cb8221f0b244aa588114d3ebf8734fb4aefd41ab802f + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -13587,7 +14083,7 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1": +"source-map-js@npm:^1.0.2, source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf @@ -14004,6 +14500,22 @@ __metadata: languageName: node linkType: hard +"styled-jsx@npm:5.1.6": + version: 5.1.6 + resolution: "styled-jsx@npm:5.1.6" + dependencies: + client-only: "npm:0.0.1" + peerDependencies: + react: ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + peerDependenciesMeta: + "@babel/core": + optional: true + babel-plugin-macros: + optional: true + checksum: 10c0/ace50e7ea5ae5ae6a3b65a50994c51fca6ae7df9c7ecfd0104c36be0b4b3a9c5c1a2374d16e2a11e256d0b20be6d47256d768ecb4f91ab390f60752a075780f5 + languageName: node + linkType: hard + "supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" @@ -14384,7 +14896,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.4.0": +"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.8.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62