Skip to content

Commit 2b55265

Browse files
andreiborzalforst
andauthored
fix(nextjs): Consider pageExtensions when looking for instrumentation file (#15701)
Closes: #15652 --------- Co-authored-by: Luca Forstner <luca.forstner@sentry.io>
1 parent 7d88266 commit 2b55265

File tree

1 file changed

+39
-55
lines changed

1 file changed

+39
-55
lines changed

packages/nextjs/src/config/webpack.ts

+39-55
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,25 @@ export function constructWebpackConfigFunction(
5454
): WebpackConfigObject {
5555
const { isServer, dev: isDev, dir: projectDir } = buildContext;
5656
const runtime = isServer ? (buildContext.nextRuntime === 'edge' ? 'edge' : 'server') : 'client';
57+
// Default page extensions per https://github.com/vercel/next.js/blob/f1dbc9260d48c7995f6c52f8fbcc65f08e627992/packages/next/server/config-shared.ts#L161
58+
const pageExtensions = userNextConfig.pageExtensions || ['tsx', 'ts', 'jsx', 'js'];
59+
const dotPrefixedPageExtensions = pageExtensions.map(ext => `.${ext}`);
60+
const pageExtensionRegex = pageExtensions.map(escapeStringForRegex).join('|');
61+
62+
// We add `.ts` and `.js` back in because `pageExtensions` might not be relevant to the instrumentation file
63+
// e.g. user's setting `.mdx`. In that case we still want to default look up
64+
// `instrumentation.ts` and `instrumentation.js`
65+
const instrumentationFile = getInstrumentationFile(projectDir, dotPrefixedPageExtensions.concat(['.ts', '.js']));
5766

5867
if (runtime !== 'client') {
59-
warnAboutDeprecatedConfigFiles(projectDir, runtime);
68+
warnAboutDeprecatedConfigFiles(projectDir, instrumentationFile, runtime);
6069
}
6170
if (runtime === 'server') {
6271
const nextJsVersion = getNextjsVersion();
6372
const { major } = parseSemver(nextJsVersion || '');
6473
// was added in v15 (https://github.com/vercel/next.js/pull/67539)
6574
if (major && major >= 15) {
66-
warnAboutMissingOnRequestErrorHandler(projectDir);
75+
warnAboutMissingOnRequestErrorHandler(instrumentationFile);
6776
}
6877
}
6978

@@ -110,11 +119,6 @@ export function constructWebpackConfigFunction(
110119
? path.join(appDirPath, '..')
111120
: projectDir;
112121

113-
// Default page extensions per https://github.com/vercel/next.js/blob/f1dbc9260d48c7995f6c52f8fbcc65f08e627992/packages/next/server/config-shared.ts#L161
114-
const pageExtensions = userNextConfig.pageExtensions || ['tsx', 'ts', 'jsx', 'js'];
115-
const dotPrefixedPageExtensions = pageExtensions.map(ext => `.${ext}`);
116-
const pageExtensionRegex = pageExtensions.map(escapeStringForRegex).join('|');
117-
118122
const staticWrappingLoaderOptions = {
119123
appDir: appDirPath,
120124
pagesDir: pagesDirPath,
@@ -445,37 +449,29 @@ async function addSentryToClientEntryProperty(
445449
}
446450

447451
/**
448-
* Make sure the instrumentation file has a `onRequestError` Handler
449-
*
450-
* @param projectDir The root directory of the project, where config files would be located
452+
* Gets the content of the user's instrumentation file
451453
*/
452-
function warnAboutMissingOnRequestErrorHandler(projectDir: string): void {
453-
const instrumentationPaths = [
454-
['src', 'instrumentation.ts'],
455-
['src', 'instrumentation.js'],
456-
['instrumentation.ts'],
457-
['instrumentation.js'],
458-
];
459-
const instrumentationFile = instrumentationPaths
460-
.map(pathSegments => path.resolve(projectDir, ...pathSegments))
461-
.find(function exists(filePath: string): string | null {
462-
try {
463-
fs.accessSync(filePath, fs.constants.F_OK);
464-
return filePath;
465-
} catch (error) {
466-
return null;
467-
}
468-
});
454+
function getInstrumentationFile(projectDir: string, dotPrefixedExtensions: string[]): string | null {
455+
const paths = dotPrefixedExtensions.flatMap(extension => [
456+
['src', `instrumentation${extension}`],
457+
[`instrumentation${extension}`],
458+
]);
469459

470-
function hasOnRequestErrorHandler(absolutePath: string): boolean {
460+
for (const pathSegments of paths) {
471461
try {
472-
const content = fs.readFileSync(absolutePath, 'utf8');
473-
return content.includes('onRequestError');
474-
} catch (error) {
475-
return false;
462+
return fs.readFileSync(path.resolve(projectDir, ...pathSegments), { encoding: 'utf-8' });
463+
} catch (e) {
464+
// no-op
476465
}
477466
}
478467

468+
return null;
469+
}
470+
471+
/**
472+
* Make sure the instrumentation file has a `onRequestError` Handler
473+
*/
474+
function warnAboutMissingOnRequestErrorHandler(instrumentationFile: string | null): void {
479475
if (!instrumentationFile) {
480476
if (!process.env.SENTRY_SUPPRESS_INSTRUMENTATION_FILE_WARNING) {
481477
// eslint-disable-next-line no-console
@@ -488,7 +484,7 @@ function warnAboutMissingOnRequestErrorHandler(projectDir: string): void {
488484
return;
489485
}
490486

491-
if (!hasOnRequestErrorHandler(instrumentationFile)) {
487+
if (!instrumentationFile.includes('onRequestError')) {
492488
// eslint-disable-next-line no-console
493489
console.warn(
494490
chalk.yellow(
@@ -505,27 +501,15 @@ function warnAboutMissingOnRequestErrorHandler(projectDir: string): void {
505501
* @param projectDir The root directory of the project, where config files would be located
506502
* @param platform Either "server" or "edge", so that we know which file to look for
507503
*/
508-
function warnAboutDeprecatedConfigFiles(projectDir: string, platform: 'server' | 'edge'): void {
509-
const hasInstrumentationHookWithIndicationsOfSentry = [
510-
['src', 'instrumentation.ts'],
511-
['src', 'instrumentation.js'],
512-
['instrumentation.ts'],
513-
['instrumentation.js'],
514-
].some(potentialInstrumentationHookPathSegments => {
515-
try {
516-
const instrumentationHookContent = fs.readFileSync(
517-
path.resolve(projectDir, ...potentialInstrumentationHookPathSegments),
518-
{ encoding: 'utf-8' },
519-
);
520-
521-
return (
522-
instrumentationHookContent.includes('@sentry/') ||
523-
instrumentationHookContent.match(/sentry\.(server|edge)\.config(\.(ts|js))?/)
524-
);
525-
} catch (e) {
526-
return false;
527-
}
528-
});
504+
function warnAboutDeprecatedConfigFiles(
505+
projectDir: string,
506+
instrumentationFile: string | null,
507+
platform: 'server' | 'edge',
508+
): void {
509+
const hasInstrumentationHookWithIndicationsOfSentry =
510+
instrumentationFile &&
511+
(instrumentationFile.includes('@sentry/') ||
512+
instrumentationFile.match(/sentry\.(server|edge)\.config(\.(ts|js))?/));
529513

530514
if (hasInstrumentationHookWithIndicationsOfSentry) {
531515
return;
@@ -535,7 +519,7 @@ function warnAboutDeprecatedConfigFiles(projectDir: string, platform: 'server' |
535519
if (fs.existsSync(path.resolve(projectDir, filename))) {
536520
// eslint-disable-next-line no-console
537521
console.warn(
538-
`[@sentry/nextjs] It appears you've configured a \`${filename}\` file. Please ensure to put this file's content into the \`register()\` function of a Next.js instrumentation hook instead. To ensure correct functionality of the SDK, \`Sentry.init\` must be called inside \`instrumentation.ts\`. Learn more about setting up an instrumentation hook in Next.js: https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation. You can safely delete the \`${filename}\` file afterward.`,
522+
`[@sentry/nextjs] It appears you've configured a \`${filename}\` file. Please ensure to put this file's content into the \`register()\` function of a Next.js instrumentation file instead. To ensure correct functionality of the SDK, \`Sentry.init\` must be called inside of an instrumentation file. Learn more about setting up an instrumentation file in Next.js: https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation. You can safely delete the \`${filename}\` file afterward.`,
539523
);
540524
}
541525
}

0 commit comments

Comments
 (0)