From 93913c2e0cd75ca23e2b5894e1156aa5fece7ce4 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Mon, 17 Mar 2025 12:01:41 +0900 Subject: [PATCH 1/3] fix(nextjs): Consider `pageExtensions` when looking for instrumentation file --- packages/nextjs/src/config/webpack.ts | 91 +++++++++++---------------- 1 file changed, 36 insertions(+), 55 deletions(-) diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 3119a70a38bd..3de2432e1c89 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -54,16 +54,22 @@ export function constructWebpackConfigFunction( ): WebpackConfigObject { const { isServer, dev: isDev, dir: projectDir } = buildContext; const runtime = isServer ? (buildContext.nextRuntime === 'edge' ? 'edge' : 'server') : 'client'; + // Default page extensions per https://github.com/vercel/next.js/blob/f1dbc9260d48c7995f6c52f8fbcc65f08e627992/packages/next/server/config-shared.ts#L161 + const pageExtensions = userNextConfig.pageExtensions || ['tsx', 'ts', 'jsx', 'js']; + const dotPrefixedPageExtensions = pageExtensions.map(ext => `.${ext}`); + const pageExtensionRegex = pageExtensions.map(escapeStringForRegex).join('|'); + + const instrumentationFile = getInstrumentationFile(projectDir, dotPrefixedPageExtensions.concat('.ts', '.js')); if (runtime !== 'client') { - warnAboutDeprecatedConfigFiles(projectDir, runtime); + warnAboutDeprecatedConfigFiles(projectDir, instrumentationFile, runtime); } if (runtime === 'server') { const nextJsVersion = getNextjsVersion(); const { major } = parseSemver(nextJsVersion || ''); // was added in v15 (https://github.com/vercel/next.js/pull/67539) if (major && major >= 15) { - warnAboutMissingOnRequestErrorHandler(projectDir); + warnAboutMissingOnRequestErrorHandler(instrumentationFile); } } @@ -110,11 +116,6 @@ export function constructWebpackConfigFunction( ? path.join(appDirPath, '..') : projectDir; - // Default page extensions per https://github.com/vercel/next.js/blob/f1dbc9260d48c7995f6c52f8fbcc65f08e627992/packages/next/server/config-shared.ts#L161 - const pageExtensions = userNextConfig.pageExtensions || ['tsx', 'ts', 'jsx', 'js']; - const dotPrefixedPageExtensions = pageExtensions.map(ext => `.${ext}`); - const pageExtensionRegex = pageExtensions.map(escapeStringForRegex).join('|'); - const staticWrappingLoaderOptions = { appDir: appDirPath, pagesDir: pagesDirPath, @@ -445,37 +446,29 @@ async function addSentryToClientEntryProperty( } /** - * Make sure the instrumentation file has a `onRequestError` Handler - * - * @param projectDir The root directory of the project, where config files would be located + * Gets the content of the user's instrumentation file */ -function warnAboutMissingOnRequestErrorHandler(projectDir: string): void { - const instrumentationPaths = [ - ['src', 'instrumentation.ts'], - ['src', 'instrumentation.js'], - ['instrumentation.ts'], - ['instrumentation.js'], - ]; - const instrumentationFile = instrumentationPaths - .map(pathSegments => path.resolve(projectDir, ...pathSegments)) - .find(function exists(filePath: string): string | null { - try { - fs.accessSync(filePath, fs.constants.F_OK); - return filePath; - } catch (error) { - return null; - } - }); +function getInstrumentationFile(projectDir: string, dotPrefixedExtensions: string[]): string | null { + const paths = dotPrefixedExtensions.flatMap(extension => [ + ['src', `instrumentation${extension}`], + [`instrumentation${extension}`], + ]); - function hasOnRequestErrorHandler(absolutePath: string): boolean { + for (const pathSegments of paths) { try { - const content = fs.readFileSync(absolutePath, 'utf8'); - return content.includes('onRequestError'); - } catch (error) { - return false; + return fs.readFileSync(path.resolve(projectDir, ...pathSegments), { encoding: 'utf-8' }); + } catch (e) { + // no-op } } + return null; +} + +/** + * Make sure the instrumentation file has a `onRequestError` Handler + */ +function warnAboutMissingOnRequestErrorHandler(instrumentationFile: string | null): void { if (!instrumentationFile) { if (!process.env.SENTRY_SUPPRESS_INSTRUMENTATION_FILE_WARNING) { // eslint-disable-next-line no-console @@ -488,7 +481,7 @@ function warnAboutMissingOnRequestErrorHandler(projectDir: string): void { return; } - if (!hasOnRequestErrorHandler(instrumentationFile)) { + if (!instrumentationFile.includes('onRequestError')) { // eslint-disable-next-line no-console console.warn( chalk.yellow( @@ -505,27 +498,15 @@ function warnAboutMissingOnRequestErrorHandler(projectDir: string): void { * @param projectDir The root directory of the project, where config files would be located * @param platform Either "server" or "edge", so that we know which file to look for */ -function warnAboutDeprecatedConfigFiles(projectDir: string, platform: 'server' | 'edge'): void { - const hasInstrumentationHookWithIndicationsOfSentry = [ - ['src', 'instrumentation.ts'], - ['src', 'instrumentation.js'], - ['instrumentation.ts'], - ['instrumentation.js'], - ].some(potentialInstrumentationHookPathSegments => { - try { - const instrumentationHookContent = fs.readFileSync( - path.resolve(projectDir, ...potentialInstrumentationHookPathSegments), - { encoding: 'utf-8' }, - ); - - return ( - instrumentationHookContent.includes('@sentry/') || - instrumentationHookContent.match(/sentry\.(server|edge)\.config(\.(ts|js))?/) - ); - } catch (e) { - return false; - } - }); +function warnAboutDeprecatedConfigFiles( + projectDir: string, + instrumentationFile: string | null, + platform: 'server' | 'edge', +): void { + const hasInstrumentationHookWithIndicationsOfSentry = + instrumentationFile && + (instrumentationFile.includes('@sentry/') || + instrumentationFile.match(/sentry\.(server|edge)\.config(\.(ts|js))?/)); if (hasInstrumentationHookWithIndicationsOfSentry) { return; @@ -535,7 +516,7 @@ function warnAboutDeprecatedConfigFiles(projectDir: string, platform: 'server' | if (fs.existsSync(path.resolve(projectDir, filename))) { // eslint-disable-next-line no-console console.warn( - `[@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.`, + `[@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 the instrumentation file. 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.`, ); } } From 0176788b6cc77c458e06099f0a83dd99b05ea791 Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Mon, 17 Mar 2025 17:48:28 +0900 Subject: [PATCH 2/3] Update packages/nextjs/src/config/webpack.ts Co-authored-by: Luca Forstner --- packages/nextjs/src/config/webpack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 3de2432e1c89..f5b96a1a4058 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -516,7 +516,7 @@ function warnAboutDeprecatedConfigFiles( if (fs.existsSync(path.resolve(projectDir, filename))) { // eslint-disable-next-line no-console console.warn( - `[@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 the instrumentation file. 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.`, + `[@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.`, ); } } From 44aa5b25147e71aee97f7f8895640ad3bc37d9b8 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Mon, 17 Mar 2025 17:54:23 +0900 Subject: [PATCH 3/3] Add ts/js extension explanation --- packages/nextjs/src/config/webpack.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index f5b96a1a4058..de047c0b8cf4 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -59,7 +59,10 @@ export function constructWebpackConfigFunction( const dotPrefixedPageExtensions = pageExtensions.map(ext => `.${ext}`); const pageExtensionRegex = pageExtensions.map(escapeStringForRegex).join('|'); - const instrumentationFile = getInstrumentationFile(projectDir, dotPrefixedPageExtensions.concat('.ts', '.js')); + // We add `.ts` and `.js` back in because `pageExtensions` might not be relevant to the instrumentation file + // e.g. user's setting `.mdx`. In that case we still want to default look up + // `instrumentation.ts` and `instrumentation.js` + const instrumentationFile = getInstrumentationFile(projectDir, dotPrefixedPageExtensions.concat(['.ts', '.js'])); if (runtime !== 'client') { warnAboutDeprecatedConfigFiles(projectDir, instrumentationFile, runtime);