Skip to content

Commit 67db14b

Browse files
author
Luca Forstner
authored
feat(nextjs): Support instrumentation-client.ts (#15705)
1 parent e242c17 commit 67db14b

File tree

11 files changed

+141
-34
lines changed

11 files changed

+141
-34
lines changed
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
import { HackComponentToRunSideEffectsInSentryClientConfig } from '../sentry.client.config';
2-
31
export default function Layout({ children }: { children: React.ReactNode }) {
42
return (
53
<html lang="en">
6-
<body>
7-
<HackComponentToRunSideEffectsInSentryClientConfig />
8-
{children}
9-
</body>
4+
<body>{children}</body>
105
</html>
116
);
127
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <p>Hello World!</p>;
3+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as Sentry from '@sentry/nextjs';
2+
3+
Sentry.init({
4+
environment: 'qa', // dynamic sampling bias to keep transactions
5+
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
6+
tunnel: `http://localhost:3031/`, // proxy server
7+
tracesSampleRate: 1.0,
8+
sendDefaultPii: true,
9+
});

dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@types/node": "^18.19.1",
1818
"@types/react": "18.0.26",
1919
"@types/react-dom": "18.0.9",
20-
"next": "15.0.0",
20+
"next": "15.3.0-canary.8",
2121
"react": "rc",
2222
"react-dom": "rc",
2323
"typescript": "~5.0.0"

dev-packages/e2e-tests/test-applications/nextjs-turbo/pages/_app.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type { AppProps } from 'next/app';
2-
import '../sentry.client.config';
32

43
export default function CustomApp({ Component, pageProps }: AppProps) {
54
return <Component {...pageProps} />;

dev-packages/e2e-tests/test-applications/nextjs-turbo/sentry.client.config.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { expect, test } from '@playwright/test';
2+
import { waitForTransaction } from '@sentry-internal/test-utils';
3+
import { parseSemver } from '@sentry/core';
4+
5+
const packageJson = require('../../package.json');
6+
const nextjsVersion = packageJson.dependencies.next;
7+
const { major, minor } = parseSemver(nextjsVersion);
8+
9+
test('Should record pageload transactions (this test verifies that the client SDK is initialized)', async ({
10+
page,
11+
}) => {
12+
// TODO: Remove this skippage when Next.js 15.3.0 is released and bump version in package json to 15.3.0
13+
test.skip(
14+
major === 15 && minor !== undefined && minor < 3,
15+
'Next.js version does not support clientside instrumentation',
16+
);
17+
18+
const pageloadTransactionPromise = waitForTransaction('nextjs-turbo', async transactionEvent => {
19+
return transactionEvent?.transaction === '/pageload-transaction';
20+
});
21+
22+
await page.goto(`/pageload-transaction`);
23+
24+
const pageloadTransaction = await pageloadTransactionPromise;
25+
26+
expect(pageloadTransaction).toBeDefined();
27+
});

dev-packages/e2e-tests/test-applications/nextjs-turbo/tests/pages-router/client-trace-propagation.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
import { expect, test } from '@playwright/test';
22
import { waitForTransaction } from '@sentry-internal/test-utils';
3+
import { parseSemver } from '@sentry/core';
4+
5+
const packageJson = require('../../package.json');
6+
const nextjsVersion = packageJson.dependencies.next;
7+
const { major, minor } = parseSemver(nextjsVersion);
38

49
test('Should propagate traces from server to client in pages router', async ({ page }) => {
10+
// TODO: Remove this skippage when Next.js 15.3.0 is released and bump version in package json to 15.3.0
11+
test.skip(
12+
major === 15 && minor !== undefined && minor < 3,
13+
'Next.js version does not support clientside instrumentation',
14+
);
15+
516
const serverTransactionPromise = waitForTransaction('nextjs-turbo', async transactionEvent => {
617
return transactionEvent?.transaction === 'GET /[param]/pages-router-client-trace-propagation';
718
});

packages/nextjs/src/config/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export type NextConfigObject = {
4444
// Next.js experimental options
4545
experimental?: {
4646
instrumentationHook?: boolean;
47+
clientInstrumentationHook?: boolean;
4748
clientTraceMetadata?: string[];
4849
};
4950
productionBrowserSourceMaps?: boolean;

packages/nextjs/src/config/webpack.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,14 @@ export function constructWebpackConfigFunction(
339339
// be fixed by using `bind`, but this is way simpler.)
340340
const origEntryProperty = newConfig.entry;
341341
newConfig.entry = async () => addSentryToClientEntryProperty(origEntryProperty, buildContext);
342+
343+
const clientSentryConfigFileName = getClientSentryConfigFile(projectDir);
344+
if (clientSentryConfigFileName) {
345+
// eslint-disable-next-line no-console
346+
console.warn(
347+
`[@sentry/nextjs] DEPRECATION WARNING: It is recommended renaming your \`${clientSentryConfigFileName}\` file, or moving its content to \`instrumentation-client.ts\`. When using Turbopack \`${clientSentryConfigFileName}\` will no longer work. Read more about the \`instrumentation-client.ts\` file: https://nextjs.org/docs/app/api-reference/config/next-config-js/clientInstrumentationHook`,
348+
);
349+
}
342350
}
343351

344352
// We don't want to do any webpack plugin stuff OR any source maps stuff in dev mode.
@@ -430,9 +438,17 @@ async function addSentryToClientEntryProperty(
430438
typeof currentEntryProperty === 'function' ? await currentEntryProperty() : { ...currentEntryProperty };
431439

432440
const clientSentryConfigFileName = getClientSentryConfigFile(projectDir);
441+
const instrumentationClientFileName = getInstrumentationClientFile(projectDir);
433442

434-
// we need to turn the filename into a path so webpack can find it
435-
const filesToInject = clientSentryConfigFileName ? [`./${clientSentryConfigFileName}`] : [];
443+
const filesToInject = [];
444+
if (clientSentryConfigFileName) {
445+
// we need to turn the filename into a path so webpack can find it
446+
filesToInject.push(`./${clientSentryConfigFileName}`);
447+
}
448+
if (instrumentationClientFileName) {
449+
// we need to turn the filename into a path so webpack can find it
450+
filesToInject.push(`./${instrumentationClientFileName}`);
451+
}
436452

437453
// inject into all entry points which might contain user's code
438454
for (const entryPointName in newEntryProperty) {
@@ -530,7 +546,7 @@ function warnAboutDeprecatedConfigFiles(
530546
*
531547
* @param projectDir The root directory of the project, where config files would be located
532548
*/
533-
export function getClientSentryConfigFile(projectDir: string): string | void {
549+
function getClientSentryConfigFile(projectDir: string): string | void {
534550
const possibilities = ['sentry.client.config.ts', 'sentry.client.config.js'];
535551

536552
for (const filename of possibilities) {
@@ -540,6 +556,26 @@ export function getClientSentryConfigFile(projectDir: string): string | void {
540556
}
541557
}
542558

559+
/**
560+
* Searches for a `instrumentation-client.ts|js` file and returns its file name if it finds one. (ts being prioritized)
561+
*
562+
* @param projectDir The root directory of the project, where config files would be located
563+
*/
564+
function getInstrumentationClientFile(projectDir: string): string | void {
565+
const possibilities = [
566+
['src', 'instrumentation-client.js'],
567+
['src', 'instrumentation-client.ts'],
568+
['instrumentation-client.js'],
569+
['instrumentation-client.ts'],
570+
];
571+
572+
for (const pathParts of possibilities) {
573+
if (fs.existsSync(path.resolve(projectDir, ...pathParts))) {
574+
return path.join(...pathParts);
575+
}
576+
}
577+
}
578+
543579
/**
544580
* Add files to a specific element of the given `entry` webpack config property.
545581
*

0 commit comments

Comments
 (0)