diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 0772f3b0b683..0134c41ca665 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -500,12 +500,12 @@ function shouldAddSentryToEntryPoint( excludeServerRoutes: Array = [], isDev: boolean, ): boolean { - if (entryPointName === 'middleware') { - return true; - } - // On the server side, by default we inject the `Sentry.init()` code into every page (with a few exceptions). if (isServer) { + if (entryPointName === 'middleware') { + return true; + } + const entryPointRoute = entryPointName.replace(/^pages/, ''); // User-specified pages to skip. (Note: For ease of use, `excludeServerRoutes` is specified in terms of routes, @@ -527,9 +527,6 @@ function shouldAddSentryToEntryPoint( // versions.) entryPointRoute === '/_app' || entryPointRoute === '/_document' || - // Newer versions of nextjs are starting to introduce things outside the `pages/` folder (middleware, an `app/` - // directory, etc), but until those features are stable and we know how we want to support them, the safest bet is - // not to inject anywhere but inside `pages/`. !entryPointName.startsWith('pages/') ) { return false; @@ -537,13 +534,11 @@ function shouldAddSentryToEntryPoint( // We want to inject Sentry into all other pages return true; - } - - // On the client side, we only want to inject into `_app`, because that guarantees there'll be only one copy of the - // SDK in the eventual bundle. Since `_app` is the (effectively) the root component for every nextjs app, inclusing - // Sentry there means it will be available for every front end page. - else { - return entryPointName === 'pages/_app'; + } else { + return ( + entryPointName === 'pages/_app' || // entrypoint for `/pages` pages + entryPointName === 'main-app' // entrypoint for `/app` pages + ); } } diff --git a/packages/nextjs/test/integration/.gitignore b/packages/nextjs/test/integration/.gitignore index 2466179ca612..b302c94ed40c 100644 --- a/packages/nextjs/test/integration/.gitignore +++ b/packages/nextjs/test/integration/.gitignore @@ -34,3 +34,6 @@ yarn-error.log* # vercel .vercel + +# Generated by Next.js 13 +.vscode diff --git a/packages/nextjs/test/integration/app/clientcomponent/layout.tsx b/packages/nextjs/test/integration/app/clientcomponent/layout.tsx new file mode 100644 index 000000000000..71beffba2865 --- /dev/null +++ b/packages/nextjs/test/integration/app/clientcomponent/layout.tsx @@ -0,0 +1,3 @@ +export default function ({ children }: { children: React.ReactNode }) { + return <>{children}; +} diff --git a/packages/nextjs/test/integration/app/clientcomponent/page.tsx b/packages/nextjs/test/integration/app/clientcomponent/page.tsx new file mode 100644 index 000000000000..a1d45b38a939 --- /dev/null +++ b/packages/nextjs/test/integration/app/clientcomponent/page.tsx @@ -0,0 +1,5 @@ +'use client'; + +export default function () { + return

I am a client component!

; +} diff --git a/packages/nextjs/test/integration/app/servercomponent/layout.tsx b/packages/nextjs/test/integration/app/servercomponent/layout.tsx new file mode 100644 index 000000000000..71beffba2865 --- /dev/null +++ b/packages/nextjs/test/integration/app/servercomponent/layout.tsx @@ -0,0 +1,3 @@ +export default function ({ children }: { children: React.ReactNode }) { + return <>{children}; +} diff --git a/packages/nextjs/test/integration/app/servercomponent/page.tsx b/packages/nextjs/test/integration/app/servercomponent/page.tsx new file mode 100644 index 000000000000..b8df1e42b948 --- /dev/null +++ b/packages/nextjs/test/integration/app/servercomponent/page.tsx @@ -0,0 +1,3 @@ +export default async function () { + return

I am a server component!

; +} diff --git a/packages/nextjs/test/integration/next12.config.template b/packages/nextjs/test/integration/next12.config.template new file mode 100644 index 000000000000..28aaba18639a --- /dev/null +++ b/packages/nextjs/test/integration/next12.config.template @@ -0,0 +1,26 @@ +const { withSentryConfig } = require('@sentry/nextjs'); + +// NOTE: This will be used by integration tests to distinguish between Webpack 4 and Webpack 5 +const moduleExports = { + webpack5: %RUN_WEBPACK_5%, + eslint: { + ignoreDuringBuilds: true, + }, + pageExtensions: ['jsx', 'js', 'tsx', 'ts', 'page.tsx'], + sentry: { + // Suppress the warning message from `handleSourcemapHidingOptionWarning` in `src/config/webpack.ts` + // TODO (v8): This can come out in v8, because this option will get a default value + hideSourceMaps: false, + excludeServerRoutes: [ + '/api/excludedEndpoints/excludedWithString', + /\/api\/excludedEndpoints\/excludedWithRegExp/, + ], + }, +}; + +const SentryWebpackPluginOptions = { + dryRun: true, + silent: true, +}; + +module.exports = withSentryConfig(moduleExports, SentryWebpackPluginOptions); diff --git a/packages/nextjs/test/integration/next13.config.template b/packages/nextjs/test/integration/next13.config.template new file mode 100644 index 000000000000..7719fae3fdf8 --- /dev/null +++ b/packages/nextjs/test/integration/next13.config.template @@ -0,0 +1,29 @@ +const { withSentryConfig } = require('@sentry/nextjs'); + +// NOTE: This will be used by integration tests to distinguish between Webpack 4 and Webpack 5 +const moduleExports = { + webpack5: %RUN_WEBPACK_5%, + eslint: { + ignoreDuringBuilds: true, + }, + experimental: { + appDir: Number(process.env.NODE_MAJOR) >= 16, // experimental.appDir requires Node v16.8.0 or later. + }, + pageExtensions: ['jsx', 'js', 'tsx', 'ts', 'page.tsx'], + sentry: { + // Suppress the warning message from `handleSourcemapHidingOptionWarning` in `src/config/webpack.ts` + // TODO (v8): This can come out in v8, because this option will get a default value + hideSourceMaps: false, + excludeServerRoutes: [ + '/api/excludedEndpoints/excludedWithString', + /\/api\/excludedEndpoints\/excludedWithRegExp/, + ], + }, +}; + +const SentryWebpackPluginOptions = { + dryRun: true, + silent: true, +}; + +module.exports = withSentryConfig(moduleExports, SentryWebpackPluginOptions); diff --git a/packages/nextjs/test/integration/pages/api/broken/index.ts b/packages/nextjs/test/integration/pages/api/broken/index.ts index 490ef1bde77d..bc1bbcd5c241 100644 --- a/packages/nextjs/test/integration/pages/api/broken/index.ts +++ b/packages/nextjs/test/integration/pages/api/broken/index.ts @@ -1,8 +1,7 @@ -import { withSentry } from '@sentry/nextjs'; import { NextApiRequest, NextApiResponse } from 'next'; const handler = async (_req: NextApiRequest, res: NextApiResponse): Promise => { res.status(500).json({ statusCode: 500, message: 'Something went wrong' }); }; -export default withSentry(handler); +export default handler; diff --git a/packages/nextjs/test/integration/pages/api/error/index.ts b/packages/nextjs/test/integration/pages/api/error/index.ts index 15f8d964f996..ce65576656de 100644 --- a/packages/nextjs/test/integration/pages/api/error/index.ts +++ b/packages/nextjs/test/integration/pages/api/error/index.ts @@ -1,8 +1,7 @@ -import { withSentry } from '@sentry/nextjs'; import { NextApiRequest, NextApiResponse } from 'next'; const handler = async (_req: NextApiRequest, _res: NextApiResponse): Promise => { throw new Error('API Error'); }; -export default withSentry(handler); +export default handler; diff --git a/packages/nextjs/test/integration/pages/api/http/index.ts b/packages/nextjs/test/integration/pages/api/http/index.ts index 958dc09ad6f6..6fe0f96964a5 100644 --- a/packages/nextjs/test/integration/pages/api/http/index.ts +++ b/packages/nextjs/test/integration/pages/api/http/index.ts @@ -1,4 +1,3 @@ -import { withSentry } from '@sentry/nextjs'; import { get } from 'http'; import { NextApiRequest, NextApiResponse } from 'next'; @@ -9,4 +8,4 @@ const handler = async (_req: NextApiRequest, res: NextApiResponse): Promise => { res.status(200).json({}); }; -export default withSentry(handler); +export default handler; diff --git a/packages/nextjs/test/integration/pages/api/wrapApiHandlerWithSentry/wrapped/[animal].ts b/packages/nextjs/test/integration/pages/api/wrapApiHandlerWithSentry/wrapped/[animal].ts index e048d9d27c0f..3307b12037d5 100644 --- a/packages/nextjs/test/integration/pages/api/wrapApiHandlerWithSentry/wrapped/[animal].ts +++ b/packages/nextjs/test/integration/pages/api/wrapApiHandlerWithSentry/wrapped/[animal].ts @@ -1,8 +1,7 @@ -import { withSentry } from '@sentry/nextjs'; import { NextApiRequest, NextApiResponse } from 'next'; const handler = async (_req: NextApiRequest, res: NextApiResponse): Promise => { res.status(200).json({}); }; -export default withSentry(handler); +export default handler; diff --git a/packages/nextjs/test/integration/pages/api/wrapApiHandlerWithSentry/wrapped/noParams.ts b/packages/nextjs/test/integration/pages/api/wrapApiHandlerWithSentry/wrapped/noParams.ts index e048d9d27c0f..3307b12037d5 100644 --- a/packages/nextjs/test/integration/pages/api/wrapApiHandlerWithSentry/wrapped/noParams.ts +++ b/packages/nextjs/test/integration/pages/api/wrapApiHandlerWithSentry/wrapped/noParams.ts @@ -1,8 +1,7 @@ -import { withSentry } from '@sentry/nextjs'; import { NextApiRequest, NextApiResponse } from 'next'; const handler = async (_req: NextApiRequest, res: NextApiResponse): Promise => { res.status(200).json({}); }; -export default withSentry(handler); +export default handler; diff --git a/packages/nextjs/test/integration/sentry.edge.config.js b/packages/nextjs/test/integration/sentry.edge.config.js new file mode 100644 index 000000000000..8d83922b637d --- /dev/null +++ b/packages/nextjs/test/integration/sentry.edge.config.js @@ -0,0 +1,7 @@ +import * as Sentry from '@sentry/nextjs'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1, + debug: process.env.SDK_DEBUG, +}); diff --git a/packages/nextjs/test/integration/test/client/appDirTracingPageloadClientcomponent.js b/packages/nextjs/test/integration/test/client/appDirTracingPageloadClientcomponent.js new file mode 100644 index 000000000000..afd746fa477a --- /dev/null +++ b/packages/nextjs/test/integration/test/client/appDirTracingPageloadClientcomponent.js @@ -0,0 +1,23 @@ +const { expectRequestCount, isTransactionRequest, expectTransaction } = require('../utils/client'); + +module.exports = async ({ page, url, requests }) => { + if (Number(process.env.NEXTJS_VERSION) < 13 || Number(process.env.NODE_MAJOR) < 16) { + // Next.js versions < 13 don't support the app directory and the app dir requires Node v16.8.0 or later. + return; + } + + const requestPromise = page.waitForRequest(isTransactionRequest); + await page.goto(`${url}/clientcomponent`); + await requestPromise; + + expectTransaction(requests.transactions[0], { + contexts: { + trace: { + op: 'pageload', + }, + }, + transaction: '/clientcomponent', + }); + + await expectRequestCount(requests, { transactions: 1 }); +}; diff --git a/packages/nextjs/test/integration/test/client/appDirTracingPageloadServercomponent.js b/packages/nextjs/test/integration/test/client/appDirTracingPageloadServercomponent.js new file mode 100644 index 000000000000..5fdd41bfdc54 --- /dev/null +++ b/packages/nextjs/test/integration/test/client/appDirTracingPageloadServercomponent.js @@ -0,0 +1,23 @@ +const { expectRequestCount, isTransactionRequest, expectTransaction } = require('../utils/client'); + +module.exports = async ({ page, url, requests }) => { + if (Number(process.env.NEXTJS_VERSION) < 13 || Number(process.env.NODE_MAJOR) < 16) { + // Next.js versions < 13 don't support the app directory and the app dir requires Node v16.8.0 or later. + return; + } + + const requestPromise = page.waitForRequest(isTransactionRequest); + await page.goto(`${url}/servercomponent`); + await requestPromise; + + expectTransaction(requests.transactions[0], { + contexts: { + trace: { + op: 'pageload', + }, + }, + transaction: '/servercomponent', + }); + + await expectRequestCount(requests, { transactions: 1 }); +}; diff --git a/packages/nextjs/test/integration/test/client/sessionCrashed.js b/packages/nextjs/test/integration/test/client/sessionCrashed.js index 0d3eacc9f143..7b6f73ed68ae 100644 --- a/packages/nextjs/test/integration/test/client/sessionCrashed.js +++ b/packages/nextjs/test/integration/test/client/sessionCrashed.js @@ -1,24 +1,27 @@ -const { waitForAll } = require('../utils/common'); -const { expectRequestCount, isSessionRequest, expectSession } = require('../utils/client'); +const { expectRequestCount, isSessionRequest, expectSession, extractEnvelopeFromRequest } = require('../utils/client'); module.exports = async ({ page, url, requests }) => { - await waitForAll([ - page.goto(`${url}/crashed`), - page.waitForRequest(isSessionRequest), - page.waitForRequest(isSessionRequest), - ]); - - expectSession(requests.sessions[0], { - init: true, - status: 'ok', - errors: 0, + const sessionRequestPromise1 = page.waitForRequest(request => { + if (isSessionRequest(request)) { + const { item } = extractEnvelopeFromRequest(request); + return item.init === true && item.status === 'ok' && item.errors === 0; + } else { + return false; + } }); - expectSession(requests.sessions[1], { - init: false, - status: 'crashed', - errors: 1, + const sessionRequestPromise2 = page.waitForRequest(request => { + if (isSessionRequest(request)) { + const { item } = extractEnvelopeFromRequest(request); + return item.init === false && item.status === 'crashed' && item.errors === 1; + } else { + return false; + } }); + await page.goto(`${url}/crashed`); + await sessionRequestPromise1; + await sessionRequestPromise2; + await expectRequestCount(requests, { sessions: 2 }); }; diff --git a/packages/nextjs/test/integration/tsconfig.json b/packages/nextjs/test/integration/tsconfig.json index 33a86b8e55c1..2c13b148d682 100644 --- a/packages/nextjs/test/integration/tsconfig.json +++ b/packages/nextjs/test/integration/tsconfig.json @@ -6,10 +6,7 @@ "forceConsistentCasingInFileNames": true, "isolatedModules": true, "jsx": "preserve", - "lib": [ - "dom", - "es2017" - ], + "lib": ["dom", "es2017"], "module": "esnext", "moduleResolution": "node", "noEmit": true, @@ -20,13 +17,13 @@ "skipLibCheck": true, "strict": true, "target": "esnext", - "incremental": true + "incremental": true, + "plugins": [ + { + "name": "next" + } + ] }, - "exclude": [ - "node_modules" - ], - "include": [ - "**/*.ts", - "**/*.tsx" - ] + "exclude": ["node_modules"], + "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"] } diff --git a/packages/nextjs/test/run-integration-tests.sh b/packages/nextjs/test/run-integration-tests.sh index a93602d681d7..2fa7cfe666ad 100755 --- a/packages/nextjs/test/run-integration-tests.sh +++ b/packages/nextjs/test/run-integration-tests.sh @@ -107,8 +107,12 @@ for NEXTJS_VERSION in 10 11 12 13; do # next 10 defaults to webpack 4 and next 11 defaults to webpack 5, but each can use either based on settings if [ "$NEXTJS_VERSION" -eq "10" ]; then sed "s/%RUN_WEBPACK_5%/$RUN_WEBPACK_5/g" next.config.js - else + elif [ "$NEXTJS_VERSION" -eq "11" ]; then sed "s/%RUN_WEBPACK_5%/$RUN_WEBPACK_5/g" next.config.js + elif [ "$NEXTJS_VERSION" -eq "12" ]; then + sed "s/%RUN_WEBPACK_5%/$RUN_WEBPACK_5/g" next.config.js + elif [ "$NEXTJS_VERSION" -eq "13" ]; then + sed "s/%RUN_WEBPACK_5%/$RUN_WEBPACK_5/g" next.config.js fi echo "[nextjs@$NEXTJS_VERSION | webpack@$WEBPACK_VERSION] Building..."