diff --git a/packages/nextjs/src/config/loaders/proxyLoader.ts b/packages/nextjs/src/config/loaders/proxyLoader.ts index 1c10bff39d01..2c499211800a 100644 --- a/packages/nextjs/src/config/loaders/proxyLoader.ts +++ b/packages/nextjs/src/config/loaders/proxyLoader.ts @@ -7,6 +7,7 @@ import { LoaderThis } from './types'; type LoaderOptions = { pagesDir: string; + pageExtensionRegex: string; }; /** @@ -16,7 +17,7 @@ type LoaderOptions = { */ export default async function proxyLoader(this: LoaderThis, userCode: string): Promise { // We know one or the other will be defined, depending on the version of webpack being used - const { pagesDir } = 'getOptions' in this ? this.getOptions() : this.query; + const { pagesDir, pageExtensionRegex } = 'getOptions' in this ? this.getOptions() : this.query; // Get the parameterized route name from this page's filepath const parameterizedRoute = path @@ -25,7 +26,7 @@ export default async function proxyLoader(this: LoaderThis, userC // Add a slash at the beginning .replace(/(.*)/, '/$1') // Pull off the file extension - .replace(/\.(jsx?|tsx?)/, '') + .replace(new RegExp(`\\.(${pageExtensionRegex})`), '') // Any page file named `index` corresponds to root of the directory its in, URL-wise, so turn `/xyz/index` into // just `/xyz` .replace(/\/index$/, '') diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index 9b4eef3e9569..b39c3777bfa0 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -32,6 +32,8 @@ export type NextConfigObject = { basePath?: string; // Config which will be available at runtime publicRuntimeConfig?: { [key: string]: unknown }; + // File extensions that count as pages in the `pages/` directory + pageExtensions?: string[]; }; export type UserSentryOptions = { diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 3eb55702d427..cfc866cd36da 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -82,13 +82,17 @@ export function constructWebpackConfigFunction( if (userSentryOptions.autoInstrumentServerFunctions) { const pagesDir = newConfig.resolve?.alias?.['private-next-pages'] as string; + // 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 pageExtensionRegex = pageExtensions.map(escapeStringForRegex).join('|'); + newConfig.module.rules.push({ // Nextjs allows the `pages` folder to optionally live inside a `src` folder - test: new RegExp(`${escapeStringForRegex(projectDir)}(/src)?/pages/.*\\.(jsx?|tsx?)`), + test: new RegExp(`${escapeStringForRegex(projectDir)}(/src)?/pages/.*\\.(${pageExtensionRegex})`), use: [ { loader: path.resolve(__dirname, 'loaders/proxyLoader.js'), - options: { pagesDir }, + options: { pagesDir, pageExtensionRegex }, }, ], }); diff --git a/packages/nextjs/test/integration/next.config.js b/packages/nextjs/test/integration/next.config.js index 267e07b19ee9..a194a9b2bd3d 100644 --- a/packages/nextjs/test/integration/next.config.js +++ b/packages/nextjs/test/integration/next.config.js @@ -4,6 +4,7 @@ const moduleExports = { eslint: { ignoreDuringBuilds: true, }, + pageExtensions: ['jsx', 'js', 'tsx', 'ts', 'page.tsx'], sentry: { autoInstrumentServerFunctions: true, // Suppress the warning message from `handleSourcemapHidingOptionWarning` in `src/config/webpack.ts` diff --git a/packages/nextjs/test/integration/next10.config.template b/packages/nextjs/test/integration/next10.config.template index 5277a715f666..78d648416c1f 100644 --- a/packages/nextjs/test/integration/next10.config.template +++ b/packages/nextjs/test/integration/next10.config.template @@ -5,6 +5,7 @@ const moduleExports = { future: { webpack5: %RUN_WEBPACK_5%, }, + pageExtensions: ['jsx', 'js', 'tsx', 'ts', 'page.tsx'], sentry: { autoInstrumentServerFunctions: true, // Suppress the warning message from `handleSourcemapHidingOptionWarning` in `src/config/webpack.ts` diff --git a/packages/nextjs/test/integration/next11.config.template b/packages/nextjs/test/integration/next11.config.template index 446f5d1f29a6..bd177f0d3e82 100644 --- a/packages/nextjs/test/integration/next11.config.template +++ b/packages/nextjs/test/integration/next11.config.template @@ -6,6 +6,7 @@ const moduleExports = { eslint: { ignoreDuringBuilds: true, }, + pageExtensions: ['jsx', 'js', 'tsx', 'ts', 'page.tsx'], sentry: { autoInstrumentServerFunctions: true, // Suppress the warning message from `handleSourcemapHidingOptionWarning` in `src/config/webpack.ts` diff --git a/packages/nextjs/test/integration/pages/customPageExtension.page.tsx b/packages/nextjs/test/integration/pages/customPageExtension.page.tsx new file mode 100644 index 000000000000..302326cdbc96 --- /dev/null +++ b/packages/nextjs/test/integration/pages/customPageExtension.page.tsx @@ -0,0 +1,12 @@ +const BasicPage = (): JSX.Element => ( +

+ This page simply exists to test the compatibility of Next.js' `pageExtensions` option with our auto wrapping + process. This file should be turned into a page by Next.js and our webpack loader should process it. +

+); + +export async function getServerSideProps() { + return { props: { data: '[some getServerSideProps data]' } }; +} + +export default BasicPage; diff --git a/packages/nextjs/test/integration/pages/unmatchedCustomPageExtension.someExtension b/packages/nextjs/test/integration/pages/unmatchedCustomPageExtension.someExtension new file mode 100644 index 000000000000..e8d58e47f18e --- /dev/null +++ b/packages/nextjs/test/integration/pages/unmatchedCustomPageExtension.someExtension @@ -0,0 +1,3 @@ +This page simply exists to test the compatibility of Next.js' `pageExtensions` option with our auto wrapping +process. This file should not be turned into a page by Next.js and our webpack loader also shouldn't process it. +This page should not contain valid JavaScript. diff --git a/packages/nextjs/test/integration/test/server/tracingServerGetServerSidePropsCustomPageExtension.js b/packages/nextjs/test/integration/test/server/tracingServerGetServerSidePropsCustomPageExtension.js new file mode 100644 index 000000000000..a2ca6a5142d1 --- /dev/null +++ b/packages/nextjs/test/integration/test/server/tracingServerGetServerSidePropsCustomPageExtension.js @@ -0,0 +1,36 @@ +const assert = require('assert'); + +const { sleep } = require('../utils/common'); +const { getAsync, interceptTracingRequest } = require('../utils/server'); + +module.exports = async ({ url: urlBase, argv }) => { + const url = `${urlBase}/customPageExtension`; + + const capturedRequest = interceptTracingRequest( + { + contexts: { + trace: { + op: 'http.server', + status: 'ok', + }, + }, + transaction: '/customPageExtension', + transaction_info: { + source: 'route', + changes: [], + propagations: 0, + }, + type: 'transaction', + request: { + url, + }, + }, + argv, + 'tracingServerGetServerSidePropsCustomPageExtension', + ); + + await getAsync(url); + await sleep(250); + + assert.ok(capturedRequest.isDone(), 'Did not intercept expected request'); +};