From 6f4b0285545f310798ef9f7d1ba5b35302b96dc1 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 13 Jan 2023 16:51:32 +0000 Subject: [PATCH 1/8] feat(nextjs): Add edge route and middleware wrappers --- packages/nextjs/jest.config.js | 1 + packages/nextjs/package.json | 3 +- packages/nextjs/src/edge/index.ts | 21 +--- packages/nextjs/src/edge/types.ts | 5 + .../nextjs/src/edge/utils/edgeWrapperUtils.ts | 102 ++++++++++++++++++ packages/nextjs/src/edge/utils/flush.ts | 20 ++++ packages/nextjs/src/edge/withSentryAPI.ts | 29 +++++ .../nextjs/src/edge/withSentryMiddleware.ts | 15 +++ packages/nextjs/src/index.types.ts | 7 ++ .../nextjs/test/edge/edgeWrapperUtils.test.ts | 76 +++++++++++++ packages/nextjs/test/setupUnitTests.ts | 1 + yarn.lock | 2 +- 12 files changed, 263 insertions(+), 19 deletions(-) create mode 100644 packages/nextjs/src/edge/types.ts create mode 100644 packages/nextjs/src/edge/utils/edgeWrapperUtils.ts create mode 100644 packages/nextjs/src/edge/utils/flush.ts create mode 100644 packages/nextjs/src/edge/withSentryAPI.ts create mode 100644 packages/nextjs/src/edge/withSentryMiddleware.ts create mode 100644 packages/nextjs/test/edge/edgeWrapperUtils.test.ts create mode 100644 packages/nextjs/test/setupUnitTests.ts diff --git a/packages/nextjs/jest.config.js b/packages/nextjs/jest.config.js index 70485db447fa..a66844865a51 100644 --- a/packages/nextjs/jest.config.js +++ b/packages/nextjs/jest.config.js @@ -5,4 +5,5 @@ module.exports = { // This prevents the build tests from running when unit tests run. (If they do, they fail, because the build being // tested hasn't necessarily run yet.) testPathIgnorePatterns: ['/test/buildProcess/'], + setupFiles: ['/test/setupUnitTests.ts'], }; diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 1b5ebf5da4c1..8a83479d86b1 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -32,7 +32,8 @@ "devDependencies": { "@types/webpack": "^4.41.31", "eslint-plugin-react": "^7.31.11", - "next": "10.1.3" + "next": "10.1.3", + "whatwg-fetch": "3.6.2" }, "peerDependencies": { "next": "^10.0.8 || ^11.0 || ^12.0 || ^13.0", diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index 42b48094d966..b0ad8ec23901 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -119,23 +119,6 @@ export async function close(timeout?: number): Promise { return Promise.resolve(false); } -/** - * Call `flush()` on the current client, if there is one. See {@link Client.flush}. - * - * @param timeout Maximum time in ms the client should wait to flush its event queue. Omitting this parameter will cause - * the client to wait until all events are sent before resolving the promise. - * @returns A promise which resolves to `true` if the queue successfully drains before the timeout, or `false` if it - * doesn't (or if there's no client defined). - */ -export async function flush(timeout?: number): Promise { - const client = getCurrentHub().getClient(); - if (client) { - return client.flush(timeout); - } - __DEBUG_BUILD__ && logger.warn('Cannot flush events. No client defined.'); - return Promise.resolve(false); -} - /** * This is the getter for lastEventId. * @@ -145,4 +128,8 @@ export function lastEventId(): string | undefined { return getCurrentHub().lastEventId(); } +export { flush } from './utils/flush'; + export * from '@sentry/core'; +export { withSentryAPI } from './withSentryAPI'; +export { withSentryMiddleware } from './withSentryMiddleware'; diff --git a/packages/nextjs/src/edge/types.ts b/packages/nextjs/src/edge/types.ts new file mode 100644 index 000000000000..81bc6796d6e0 --- /dev/null +++ b/packages/nextjs/src/edge/types.ts @@ -0,0 +1,5 @@ +// We cannot make any assumptions about what users define as their handler except maybe that it is a function +export interface EdgeRouteHandler { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (req: any): any | Promise; +} diff --git a/packages/nextjs/src/edge/utils/edgeWrapperUtils.ts b/packages/nextjs/src/edge/utils/edgeWrapperUtils.ts new file mode 100644 index 000000000000..5ccda34a6ddf --- /dev/null +++ b/packages/nextjs/src/edge/utils/edgeWrapperUtils.ts @@ -0,0 +1,102 @@ +import { captureException, getCurrentHub, startTransaction } from '@sentry/core'; +import { hasTracingEnabled } from '@sentry/tracing'; +import type { Span } from '@sentry/types'; +import { + addExceptionMechanism, + baggageHeaderToDynamicSamplingContext, + extractTraceparentData, + logger, + objectify, +} from '@sentry/utils'; + +import type { EdgeRouteHandler } from '../types'; +import { flush } from './flush'; + +/** + * Wraps a function on the edge runtime with error and performance monitoring. + */ +export function withEdgeWrapping( + handler: H, + options: { spanLabel: string; spanOp: string; mechanismFunctionName: string }, +): (...params: Parameters) => Promise> { + return async function (this: unknown, ...args) { + const req = args[0]; + const currentScope = getCurrentHub().getScope(); + const prevSpan = currentScope?.getSpan(); + + let span: Span | undefined; + + if (hasTracingEnabled()) { + if (prevSpan) { + span = prevSpan.startChild({ + description: options.spanLabel, + op: options.spanOp, + }); + } else if (req instanceof Request) { + // If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision) + let traceparentData; + + const sentryTraceHeader = req.headers.get('sentry-trace'); + if (sentryTraceHeader) { + traceparentData = extractTraceparentData(sentryTraceHeader); + __DEBUG_BUILD__ && logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`); + } + + const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(req.headers.get('baggage')); + + span = startTransaction( + { + name: options.spanLabel, + op: options.spanOp, + ...traceparentData, + metadata: { + dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, + source: 'route', + }, + }, + // extra context passed to the `tracesSampler` + { request: req }, + ); + } + + currentScope?.setSpan(span); + } + + try { + const handlerResult: ReturnType = await handler.apply(this, args); + + if ((handlerResult as unknown) instanceof Response) { + span?.setHttpStatus(handlerResult.status); + } else { + span?.setStatus('ok'); + } + + return handlerResult; + } catch (e) { + // In case we have a primitive, wrap it in the equivalent wrapper class (string -> String, etc.) so that we can + // store a seen flag on it. + const objectifiedErr = objectify(e); + + currentScope?.addEventProcessor(event => { + addExceptionMechanism(event, { + type: 'instrument', + handled: false, + data: { + function: options.mechanismFunctionName, + }, + }); + return event; + }); + + span?.setStatus('internal_error'); + + captureException(objectifiedErr); + + throw objectifiedErr; + } finally { + span?.finish(); + currentScope?.setSpan(prevSpan); + await flush(2000); + } + }; +} diff --git a/packages/nextjs/src/edge/utils/flush.ts b/packages/nextjs/src/edge/utils/flush.ts new file mode 100644 index 000000000000..5daa52936391 --- /dev/null +++ b/packages/nextjs/src/edge/utils/flush.ts @@ -0,0 +1,20 @@ +import { getCurrentHub } from '@sentry/core'; +import type { Client } from '@sentry/types'; +import { logger } from '@sentry/utils'; + +/** + * Call `flush()` on the current client, if there is one. See {@link Client.flush}. + * + * @param timeout Maximum time in ms the client should wait to flush its event queue. Omitting this parameter will cause + * the client to wait until all events are sent before resolving the promise. + * @returns A promise which resolves to `true` if the queue successfully drains before the timeout, or `false` if it + * doesn't (or if there's no client defined). + */ +export async function flush(timeout?: number): Promise { + const client = getCurrentHub().getClient(); + if (client) { + return client.flush(timeout); + } + __DEBUG_BUILD__ && logger.warn('Cannot flush events. No client defined.'); + return Promise.resolve(false); +} diff --git a/packages/nextjs/src/edge/withSentryAPI.ts b/packages/nextjs/src/edge/withSentryAPI.ts new file mode 100644 index 000000000000..26e567903acf --- /dev/null +++ b/packages/nextjs/src/edge/withSentryAPI.ts @@ -0,0 +1,29 @@ +import { getCurrentHub } from '@sentry/core'; + +import type { EdgeRouteHandler } from './types'; +import { withEdgeWrapping } from './utils/edgeWrapperUtils'; + +/** + * Wraps a Next.js edge route handler with Sentry error and performance instrumentation. + */ +export function withSentryAPI( + handler: H, + parameterizedRoute: string, +): (...params: Parameters) => Promise> { + return async function (this: unknown, ...args: Parameters): Promise> { + const req = args[0]; + + const isCalledByUser = getCurrentHub().getScope()?.getTransaction(); + + const wrappedHandler = withEdgeWrapping(handler, { + spanLabel: + isCalledByUser || !(req instanceof Request) + ? `handler (${parameterizedRoute})` + : `${req.method} ${parameterizedRoute}`, + spanOp: isCalledByUser ? 'function' : 'http.server', + mechanismFunctionName: 'withSentryAPI', + }); + + return await wrappedHandler.apply(this, args); + }; +} diff --git a/packages/nextjs/src/edge/withSentryMiddleware.ts b/packages/nextjs/src/edge/withSentryMiddleware.ts new file mode 100644 index 000000000000..5bc2480eacb2 --- /dev/null +++ b/packages/nextjs/src/edge/withSentryMiddleware.ts @@ -0,0 +1,15 @@ +import type { EdgeRouteHandler } from './types'; +import { withEdgeWrapping } from './utils/edgeWrapperUtils'; + +/** + * Wraps Next.js middleware with Sentry error and performance instrumentation. + */ +export function withSentryMiddleware( + middleware: H, +): (...params: Parameters) => Promise> { + return withEdgeWrapping(middleware, { + spanLabel: 'middleware', + spanOp: 'middleware.nextjs', + mechanismFunctionName: 'withSentryMiddleware', + }); +} diff --git a/packages/nextjs/src/index.types.ts b/packages/nextjs/src/index.types.ts index fcce6708a293..9f82be80a67c 100644 --- a/packages/nextjs/src/index.types.ts +++ b/packages/nextjs/src/index.types.ts @@ -29,3 +29,10 @@ export declare function close(timeout?: number | undefined): PromiseLike; export declare function lastEventId(): string | undefined; export declare function getSentryRelease(fallback?: string): string | undefined; + +export declare function withSentryAPI any>( + handler: APIHandler, + parameterizedRoute: string, +): ( + ...args: Parameters +) => ReturnType extends Promise ? ReturnType : Promise>; diff --git a/packages/nextjs/test/edge/edgeWrapperUtils.test.ts b/packages/nextjs/test/edge/edgeWrapperUtils.test.ts new file mode 100644 index 000000000000..1f34e81aea57 --- /dev/null +++ b/packages/nextjs/test/edge/edgeWrapperUtils.test.ts @@ -0,0 +1,76 @@ +import * as coreSdk from '@sentry/core'; +import * as sentryTracing from '@sentry/tracing'; + +import { withEdgeWrapping } from '../../src/edge/utils/edgeWrapperUtils'; + +jest.spyOn(sentryTracing, 'hasTracingEnabled').mockImplementation(() => true); + +describe('withEdgeWrapping', () => { + it('should return a function that calls the passed function', async () => { + const origFunctionReturnValue = new Response(); + const origFunction = jest.fn(_req => origFunctionReturnValue); + + const wrappedFunction = withEdgeWrapping(origFunction, { + spanLabel: 'some label', + mechanismFunctionName: 'some name', + spanOp: 'some op', + }); + + const returnValue = await wrappedFunction(new Request('https://sentry.io/')); + + expect(returnValue).toBe(origFunctionReturnValue); + expect(origFunction).toHaveBeenCalledTimes(1); + }); + + it('should return a function that calls captureException on error', async () => { + const captureExceptionSpy = jest.spyOn(coreSdk, 'captureException'); + const error = new Error(); + const origFunction = jest.fn(_req => { + throw error; + }); + + const wrappedFunction = withEdgeWrapping(origFunction, { + spanLabel: 'some label', + mechanismFunctionName: 'some name', + spanOp: 'some op', + }); + + await expect(wrappedFunction(new Request('https://sentry.io/'))).rejects.toBe(error); + expect(captureExceptionSpy).toHaveBeenCalledTimes(1); + }); + + it('should return a function that starts a transaction when a request object is passed', async () => { + const startTransactionSpy = jest.spyOn(coreSdk, 'startTransaction'); + + const origFunctionReturnValue = new Response(); + const origFunction = jest.fn(_req => origFunctionReturnValue); + + const wrappedFunction = withEdgeWrapping(origFunction, { + spanLabel: 'some label', + mechanismFunctionName: 'some name', + spanOp: 'some op', + }); + + const request = new Request('https://sentry.io/'); + await wrappedFunction(request); + expect(startTransactionSpy).toHaveBeenCalledTimes(1); + expect(startTransactionSpy).toHaveBeenCalledWith( + expect.objectContaining({ metadata: { source: 'route' }, name: 'some label', op: 'some op' }), + { request }, + ); + }); + + it("should return a function that doesn't crash when req isn't passed", async () => { + const origFunctionReturnValue = new Response(); + const origFunction = jest.fn(() => origFunctionReturnValue); + + const wrappedFunction = withEdgeWrapping(origFunction, { + spanLabel: 'some label', + mechanismFunctionName: 'some name', + spanOp: 'some op', + }); + + await expect(wrappedFunction()).resolves.toBe(origFunctionReturnValue); + expect(origFunction).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/nextjs/test/setupUnitTests.ts b/packages/nextjs/test/setupUnitTests.ts new file mode 100644 index 000000000000..754f5df863af --- /dev/null +++ b/packages/nextjs/test/setupUnitTests.ts @@ -0,0 +1 @@ +import 'whatwg-fetch'; // polyfill fetch/Request/Response globals which edge routes need diff --git a/yarn.lock b/yarn.lock index c2dbdbaef239..dd3dd1b862d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25053,7 +25053,7 @@ whatwg-encoding@^2.0.0: dependencies: iconv-lite "0.6.3" -whatwg-fetch@>=0.10.0: +whatwg-fetch@3.6.2, whatwg-fetch@>=0.10.0: version "3.6.2" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== From 416a7c87ada7072bdc67dfd5ad2324c5877fbac8 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 13 Jan 2023 16:52:28 +0000 Subject: [PATCH 2/8] feat(nextjs): Auto-wrap edge-routes and middleware --- packages/nextjs/rollup.npm.config.js | 6 ++- .../src/config/loaders/wrappingLoader.ts | 21 +++++--- .../templates/middlewareWrapperTemplate.ts | 53 +++++++++++++++++++ packages/nextjs/src/config/types.ts | 2 +- packages/nextjs/src/config/webpack.ts | 46 ++++++++++++++-- 5 files changed, 115 insertions(+), 13 deletions(-) create mode 100644 packages/nextjs/src/config/templates/middlewareWrapperTemplate.ts diff --git a/packages/nextjs/rollup.npm.config.js b/packages/nextjs/rollup.npm.config.js index db1fa9c1fde1..5268746a7e63 100644 --- a/packages/nextjs/rollup.npm.config.js +++ b/packages/nextjs/rollup.npm.config.js @@ -14,7 +14,11 @@ export default [ ), ...makeNPMConfigVariants( makeBaseNPMConfig({ - entrypoints: ['src/config/templates/pageWrapperTemplate.ts', 'src/config/templates/apiWrapperTemplate.ts'], + entrypoints: [ + 'src/config/templates/pageWrapperTemplate.ts', + 'src/config/templates/apiWrapperTemplate.ts', + 'src/config/templates/middlewareWrapperTemplate.ts', + ], packageSpecificConfig: { output: { diff --git a/packages/nextjs/src/config/loaders/wrappingLoader.ts b/packages/nextjs/src/config/loaders/wrappingLoader.ts index f092216bbf1d..5dee8983ef11 100644 --- a/packages/nextjs/src/config/loaders/wrappingLoader.ts +++ b/packages/nextjs/src/config/loaders/wrappingLoader.ts @@ -12,6 +12,9 @@ const apiWrapperTemplateCode = fs.readFileSync(apiWrapperTemplatePath, { encodin const pageWrapperTemplatePath = path.resolve(__dirname, '..', 'templates', 'pageWrapperTemplate.js'); const pageWrapperTemplateCode = fs.readFileSync(pageWrapperTemplatePath, { encoding: 'utf8' }); +const middlewareWrapperTemplatePath = path.resolve(__dirname, '..', 'templates', 'middlewareWrapperTemplate.js'); +const middlewareWrapperTemplateCode = fs.readFileSync(middlewareWrapperTemplatePath, { encoding: 'utf8' }); + // Just a simple placeholder to make referencing module consistent const SENTRY_WRAPPER_MODULE_NAME = 'sentry-wrapper-module'; @@ -40,14 +43,8 @@ export default function wrappingLoader( pagesDir, pageExtensionRegex, excludeServerRoutes = [], - isEdgeRuntime, } = 'getOptions' in this ? this.getOptions() : this.query; - // We currently don't support the edge runtime - if (isEdgeRuntime) { - return userCode; - } - this.async(); // Get the parameterized route name from this page's filepath @@ -71,7 +68,17 @@ export default function wrappingLoader( return; } - let templateCode = parameterizedRoute.startsWith('/api') ? apiWrapperTemplateCode : pageWrapperTemplateCode; + const middlewareJsPath = path.join(pagesDir, '..', 'middleware.js'); + const middlewareTsPath = path.join(pagesDir, '..', 'middleware.js'); + + let templateCode: string; + if (parameterizedRoute.startsWith('/api')) { + templateCode = apiWrapperTemplateCode; + } else if (this.resourcePath === middlewareJsPath || this.resourcePath === middlewareTsPath) { + templateCode = middlewareWrapperTemplateCode; + } else { + templateCode = pageWrapperTemplateCode; + } // Inject the route and the path to the file we're wrapping into the template templateCode = templateCode.replace(/__ROUTE__/g, parameterizedRoute.replace(/\\/g, '\\\\')); diff --git a/packages/nextjs/src/config/templates/middlewareWrapperTemplate.ts b/packages/nextjs/src/config/templates/middlewareWrapperTemplate.ts new file mode 100644 index 000000000000..40cfedfce8d8 --- /dev/null +++ b/packages/nextjs/src/config/templates/middlewareWrapperTemplate.ts @@ -0,0 +1,53 @@ +/** + * This file is a template for the code which will be substituted when our webpack loader handles API files in the + * `pages/` directory. + * + * We use `__RESOURCE_PATH__` as a placeholder for the path to the file being wrapped. Because it's not a real package, + * this causes both TS and ESLint to complain, hence the pragma comments below. + */ + +// @ts-ignore See above +// eslint-disable-next-line import/no-unresolved +import * as origModule from '__SENTRY_WRAPPING_TARGET__'; +// eslint-disable-next-line import/no-extraneous-dependencies +import * as Sentry from '@sentry/nextjs'; + +// We import this from `wrappers` rather than directly from `next` because our version can work simultaneously with +// multiple versions of next. See note in `wrappers/types` for more. +import type { NextApiHandler } from '../../server/types'; + +type NextApiModule = + | { + // ESM export + default?: NextApiHandler; // TODO CHANGE THIS TYPE + middleware?: NextApiHandler; // TODO CHANGE THIS TYPE + } + // CJS export + | NextApiHandler; + +const userApiModule = origModule as NextApiModule; + +// Default to undefined. It's possible for Next.js users to not define any exports/handlers in an API route. If that is +// the case Next.js wil crash during runtime but the Sentry SDK should definitely not crash so we need tohandle it. +let userProvidedNamedHandler: NextApiHandler | undefined = undefined; +let userProvidedDefaultHandler: NextApiHandler | undefined = undefined; + +if ('middleware' in userApiModule && typeof userApiModule.middleware === 'function') { + // Handle when user defines via named ESM export: `export { middleware };` + userProvidedNamedHandler = userApiModule.middleware; +} else if ('default' in userApiModule && typeof userApiModule.default === 'function') { + // Handle when user defines via ESM export: `export default myFunction;` + userProvidedDefaultHandler = userApiModule.default; +} else if (typeof userApiModule === 'function') { + // Handle when user defines via CJS export: "module.exports = myFunction;" + userProvidedDefaultHandler = userApiModule; +} + +export const middleware = userProvidedNamedHandler ? Sentry.withSentryMiddleware(userProvidedNamedHandler) : undefined; +export default userProvidedDefaultHandler ? Sentry.withSentryMiddleware(userProvidedDefaultHandler) : undefined; + +// Re-export anything exported by the page module we're wrapping. When processing this code, Rollup is smart enough to +// not include anything whose name matchs something we've explicitly exported above. +// @ts-ignore See above +// eslint-disable-next-line import/no-unresolved +export * from '__SENTRY_WRAPPING_TARGET__'; diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index b79243627432..0f8ee9d5c9c6 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -164,7 +164,7 @@ export type EntryPointObject = { import: string | Array }; */ export type WebpackModuleRule = { - test?: string | RegExp; + test?: string | RegExp | ((resourcePath: string) => boolean); include?: Array | RegExp; exclude?: (filepath: string) => boolean; use?: ModuleRuleUseProperty | Array; diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index b5a8d07db2fd..b3281b3ac020 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -99,23 +99,61 @@ export function constructWebpackConfigFunction( if (isServer) { if (userSentryOptions.autoInstrumentServerFunctions !== false) { - const pagesDir = newConfig.resolve?.alias?.['private-next-pages'] as string; + let pagesDirPath: string; + if ( + fs.existsSync(path.join(projectDir, 'pages')) && + fs.lstatSync(path.join(projectDir, 'pages')).isDirectory() + ) { + pagesDirPath = path.join(projectDir, 'pages'); + } else { + pagesDirPath = path.join(projectDir, 'src', 'pages'); + } + + const middlewareJsPath = path.join(pagesDirPath, '..', 'middleware.js'); + const middlewareTsPath = path.join(pagesDirPath, '..', 'middleware.ts'); // 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('|'); // It is very important that we insert our loader at the beginning of the array because we expect any sort of transformations/transpilations (e.g. TS -> JS) to already have happened. newConfig.module.rules.unshift({ - test: new RegExp(`^${escapeStringForRegex(pagesDir)}.*\\.(${pageExtensionRegex})$`), + test: resourcePath => { + // We generally want to apply the loader to all API routes, pages and to the middleware file. + + // `resourcePath` may be an absolute path or a path relative to the context of the webpack config + let absoluteResourcePath: string; + if (path.isAbsolute(resourcePath)) { + absoluteResourcePath = resourcePath; + } else { + absoluteResourcePath = path.join(projectDir, resourcePath); + } + const normalizedAbsoluteResourcePath = path.normalize(absoluteResourcePath); + + if ( + // Match everything inside pages/ with the appropriate file extension + normalizedAbsoluteResourcePath.startsWith(pagesDirPath) && + dotPrefixedPageExtensions.some(ext => normalizedAbsoluteResourcePath.endsWith(ext)) + ) { + return true; + } else if ( + // Match middleware.js and middleware.ts + normalizedAbsoluteResourcePath === middlewareJsPath || + normalizedAbsoluteResourcePath === middlewareTsPath + ) { + return true; + } else { + return false; + } + }, use: [ { loader: path.resolve(__dirname, 'loaders/wrappingLoader.js'), options: { - pagesDir, + pagesDir: pagesDirPath, pageExtensionRegex, excludeServerRoutes: userSentryOptions.excludeServerRoutes, - isEdgeRuntime: buildContext.nextRuntime === 'edge', }, }, ], From a7c103dae79229c6a35642a999d676c845042071 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 13 Jan 2023 17:58:19 +0000 Subject: [PATCH 3/8] Fix tests on node 10 --- packages/nextjs/jest.config.js | 1 - packages/nextjs/package.json | 3 +-- .../nextjs/test/edge/edgeWrapperUtils.test.ts | 24 +++++++++++++++++++ packages/nextjs/test/setupUnitTests.ts | 1 - yarn.lock | 2 +- 5 files changed, 26 insertions(+), 5 deletions(-) delete mode 100644 packages/nextjs/test/setupUnitTests.ts diff --git a/packages/nextjs/jest.config.js b/packages/nextjs/jest.config.js index a66844865a51..70485db447fa 100644 --- a/packages/nextjs/jest.config.js +++ b/packages/nextjs/jest.config.js @@ -5,5 +5,4 @@ module.exports = { // This prevents the build tests from running when unit tests run. (If they do, they fail, because the build being // tested hasn't necessarily run yet.) testPathIgnorePatterns: ['/test/buildProcess/'], - setupFiles: ['/test/setupUnitTests.ts'], }; diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 8a83479d86b1..1b5ebf5da4c1 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -32,8 +32,7 @@ "devDependencies": { "@types/webpack": "^4.41.31", "eslint-plugin-react": "^7.31.11", - "next": "10.1.3", - "whatwg-fetch": "3.6.2" + "next": "10.1.3" }, "peerDependencies": { "next": "^10.0.8 || ^11.0 || ^12.0 || ^13.0", diff --git a/packages/nextjs/test/edge/edgeWrapperUtils.test.ts b/packages/nextjs/test/edge/edgeWrapperUtils.test.ts index 1f34e81aea57..1dc82049ed81 100644 --- a/packages/nextjs/test/edge/edgeWrapperUtils.test.ts +++ b/packages/nextjs/test/edge/edgeWrapperUtils.test.ts @@ -5,6 +5,30 @@ import { withEdgeWrapping } from '../../src/edge/utils/edgeWrapperUtils'; jest.spyOn(sentryTracing, 'hasTracingEnabled').mockImplementation(() => true); +// @ts-ignore Request does not exist on type Global +const origRequest = global.Request; +// @ts-ignore Response does not exist on type Global +const origResponse = global.Response; + +// @ts-ignore Request does not exist on type Global +global.Request = class Request { + headers = { + get() { + return null; + }, + }; +}; + +// @ts-ignore Response does not exist on type Global +global.Response = class Request {}; + +afterAll(() => { + // @ts-ignore Request does not exist on type Global + global.Request = origRequest; + // @ts-ignore Response does not exist on type Global + global.Response = origResponse; +}); + describe('withEdgeWrapping', () => { it('should return a function that calls the passed function', async () => { const origFunctionReturnValue = new Response(); diff --git a/packages/nextjs/test/setupUnitTests.ts b/packages/nextjs/test/setupUnitTests.ts deleted file mode 100644 index 754f5df863af..000000000000 --- a/packages/nextjs/test/setupUnitTests.ts +++ /dev/null @@ -1 +0,0 @@ -import 'whatwg-fetch'; // polyfill fetch/Request/Response globals which edge routes need diff --git a/yarn.lock b/yarn.lock index dd3dd1b862d6..c2dbdbaef239 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25053,7 +25053,7 @@ whatwg-encoding@^2.0.0: dependencies: iconv-lite "0.6.3" -whatwg-fetch@3.6.2, whatwg-fetch@>=0.10.0: +whatwg-fetch@>=0.10.0: version "3.6.2" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== From 918e831f4e508114a2b91cf079195cd2d67a968b Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 13 Jan 2023 18:16:45 +0000 Subject: [PATCH 4/8] Cosmetics --- .../config/templates/apiWrapperTemplate.ts | 4 ++-- .../templates/middlewareWrapperTemplate.ts | 21 ++++++++----------- .../config/templates/pageWrapperTemplate.ts | 4 ++-- packages/nextjs/src/edge/types.ts | 2 +- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/nextjs/src/config/templates/apiWrapperTemplate.ts b/packages/nextjs/src/config/templates/apiWrapperTemplate.ts index 2f8dd2184301..4d06dfb369a0 100644 --- a/packages/nextjs/src/config/templates/apiWrapperTemplate.ts +++ b/packages/nextjs/src/config/templates/apiWrapperTemplate.ts @@ -1,8 +1,8 @@ -/** +/* * This file is a template for the code which will be substituted when our webpack loader handles API files in the * `pages/` directory. * - * We use `__RESOURCE_PATH__` as a placeholder for the path to the file being wrapped. Because it's not a real package, + * We use `__SENTRY_WRAPPING_TARGET__` as a placeholder for the path to the file being wrapped. Because it's not a real package, * this causes both TS and ESLint to complain, hence the pragma comments below. */ diff --git a/packages/nextjs/src/config/templates/middlewareWrapperTemplate.ts b/packages/nextjs/src/config/templates/middlewareWrapperTemplate.ts index 40cfedfce8d8..5bca0012ab5d 100644 --- a/packages/nextjs/src/config/templates/middlewareWrapperTemplate.ts +++ b/packages/nextjs/src/config/templates/middlewareWrapperTemplate.ts @@ -1,8 +1,7 @@ -/** - * This file is a template for the code which will be substituted when our webpack loader handles API files in the - * `pages/` directory. +/* + * This file is a template for the code which will be substituted when our webpack loader handles middleware files. * - * We use `__RESOURCE_PATH__` as a placeholder for the path to the file being wrapped. Because it's not a real package, + * We use `__SENTRY_WRAPPING_TARGET__` as a placeholder for the path to the file being wrapped. Because it's not a real package, * this causes both TS and ESLint to complain, hence the pragma comments below. */ @@ -12,25 +11,23 @@ import * as origModule from '__SENTRY_WRAPPING_TARGET__'; // eslint-disable-next-line import/no-extraneous-dependencies import * as Sentry from '@sentry/nextjs'; -// We import this from `wrappers` rather than directly from `next` because our version can work simultaneously with -// multiple versions of next. See note in `wrappers/types` for more. -import type { NextApiHandler } from '../../server/types'; +import type { EdgeRouteHandler } from '../../edge/types'; type NextApiModule = | { // ESM export - default?: NextApiHandler; // TODO CHANGE THIS TYPE - middleware?: NextApiHandler; // TODO CHANGE THIS TYPE + default?: EdgeRouteHandler; + middleware?: EdgeRouteHandler; } // CJS export - | NextApiHandler; + | EdgeRouteHandler; const userApiModule = origModule as NextApiModule; // Default to undefined. It's possible for Next.js users to not define any exports/handlers in an API route. If that is // the case Next.js wil crash during runtime but the Sentry SDK should definitely not crash so we need tohandle it. -let userProvidedNamedHandler: NextApiHandler | undefined = undefined; -let userProvidedDefaultHandler: NextApiHandler | undefined = undefined; +let userProvidedNamedHandler: EdgeRouteHandler | undefined = undefined; +let userProvidedDefaultHandler: EdgeRouteHandler | undefined = undefined; if ('middleware' in userApiModule && typeof userApiModule.middleware === 'function') { // Handle when user defines via named ESM export: `export { middleware };` diff --git a/packages/nextjs/src/config/templates/pageWrapperTemplate.ts b/packages/nextjs/src/config/templates/pageWrapperTemplate.ts index e3b6b4e7e296..db8385c97f81 100644 --- a/packages/nextjs/src/config/templates/pageWrapperTemplate.ts +++ b/packages/nextjs/src/config/templates/pageWrapperTemplate.ts @@ -1,8 +1,8 @@ -/** +/* * This file is a template for the code which will be substituted when our webpack loader handles non-API files in the * `pages/` directory. * - * We use `__RESOURCE_PATH__` as a placeholder for the path to the file being wrapped. Because it's not a real package, + * We use `__SENTRY_WRAPPING_TARGET__` as a placeholder for the path to the file being wrapped. Because it's not a real package, * this causes both TS and ESLint to complain, hence the pragma comments below. */ diff --git a/packages/nextjs/src/edge/types.ts b/packages/nextjs/src/edge/types.ts index 81bc6796d6e0..71f96ec1946b 100644 --- a/packages/nextjs/src/edge/types.ts +++ b/packages/nextjs/src/edge/types.ts @@ -1,5 +1,5 @@ // We cannot make any assumptions about what users define as their handler except maybe that it is a function export interface EdgeRouteHandler { // eslint-disable-next-line @typescript-eslint/no-explicit-any - (req: any): any | Promise; + (...args: any[]): any; } From 2791323f78f4cfd18f44351382561b3c57b4bb8f Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 13 Jan 2023 18:29:26 +0000 Subject: [PATCH 5/8] Add js doc --- packages/nextjs/src/config/types.ts | 64 ++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index 0f8ee9d5c9c6..da299f9ffcd3 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -22,6 +22,7 @@ export type ExportedNextConfig = NextConfigObjectWithSentry | NextConfigFunction export type NextConfigObjectWithSentry = NextConfigObject & { sentry?: UserSentryOptions; }; + export type NextConfigFunctionWithSentry = ( phase: string, defaults: { defaultConfig: NextConfigObject }, @@ -60,39 +61,62 @@ export type NextConfigObject = { }; export type UserSentryOptions = { - // Override the SDK's default decision about whether or not to enable to the webpack plugin. Note that `false` forces - // the plugin to be enabled, even in situations where it's not recommended. + /** + * Override the SDK's default decision about whether or not to enable to the Sentry webpack plugin for server files. + * Note that `false` forces the plugin to be enabled, even in situations where it's not recommended. + */ disableServerWebpackPlugin?: boolean; + + /** + * Override the SDK's default decision about whether or not to enable to the Sentry webpack plugin for client files. + * Note that `false` forces the plugin to be enabled, even in situations where it's not recommended. + */ disableClientWebpackPlugin?: boolean; - // Use `hidden-source-map` for webpack `devtool` option, which strips the `sourceMappingURL` from the bottom of built - // JS files + /** + * Use `hidden-source-map` for webpack `devtool` option, which strips the `sourceMappingURL` from the bottom of built + * JS files. + */ hideSourceMaps?: boolean; - // Force webpack to apply the same transpilation rules to the SDK code as apply to user code. Helpful when targeting - // older browsers which don't support ES6 (or ES6+ features like object spread). + /** + * Instructs webpack to apply the same transpilation rules to the SDK code as apply to user code. Helpful when + * targeting older browsers which don't support ES6 (or ES6+ features like object spread). + */ transpileClientSDK?: boolean; - // Upload files from `/static/chunks` rather than `/static/chunks/pages`. Usually files outside of - // `pages/` only contain third-party code, but in cases where they contain user code, restricting the webpack - // plugin's upload breaks sourcemaps for those user-code-containing files, because it keeps them from being - // uploaded. At the same time, we don't want to widen the scope if we don't have to, because we're guaranteed to end - // up uploading too many files, which is why this defaults to `false`. + /** + * Instructs the Sentry webpack plugin to upload source files from `/static/chunks` rather than + * `/static/chunks/pages`. Usually files outside of `pages/` only contain third-party code, but in cases + * where they contain user code, restricting the webpack plugin's upload breaks sourcemaps for those + * user-code-containing files, because it keeps them from being uploaded. Defaults to `false`. + */ + // We don't want to widen the scope if we don't have to, because we're guaranteed to end up uploading too many files, + // which is why this defaults to`false`. widenClientFileUpload?: boolean; - // Automatically instrument Next.js data fetching methods and Next.js API routes + /** + * Automatically instrument Next.js data fetching methods and Next.js API routes with error and performance monitoring. + * Defaults to `true`. + */ autoInstrumentServerFunctions?: boolean; - // Exclude certain serverside API routes or pages from being instrumented with Sentry. This option takes an array of - // strings or regular expressions. - // - // NOTE: Pages should be specified as routes (`/animals` or `/api/animals/[animalType]/habitat`), not filepaths - // (`pages/animals/index.js` or `.\src\pages\api\animals\[animalType]\habitat.tsx`), and strings must be be a full, - // exact match. + /** + * Exclude certain serverside API routes or pages from being instrumented with Sentry. This option takes an array of + * strings or regular expressions. + * + * NOTE: Pages should be specified as routes (`/animals` or `/api/animals/[animalType]/habitat`), not filepaths + * (`pages/animals/index.js` or `.\src\pages\api\animals\[animalType]\habitat.tsx`), and strings must be be a full, + * exact match. + */ excludeServerRoutes?: Array; - // Tunnel Sentry requests through this route on the Next.js server, to circumvent ad-blockers blocking Sentry events from being sent. - // This option should be a path (for example: '/error-monitoring'). + /** + * Tunnel Sentry requests through this route on the Next.js server, to circumvent ad-blockers blocking Sentry events + * from being sent. This option should be a path (for example: '/error-monitoring'). + * + * NOTE: This feature only works with Next.js 11+ + */ tunnelRoute?: string; }; From a1bf405d6c480d02bc4692267fca682c9d317461 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 13 Jan 2023 18:40:19 +0000 Subject: [PATCH 6/8] Add option to disable auto wrapping of middleware --- packages/nextjs/src/config/types.ts | 5 +++++ packages/nextjs/src/config/webpack.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index da299f9ffcd3..dd7a18f2f821 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -101,6 +101,11 @@ export type UserSentryOptions = { */ autoInstrumentServerFunctions?: boolean; + /** + * Automatically instrument Next.js middleware with error and performance monitoring. Defaults to `true`. + */ + autoInstrumentMiddleware?: boolean; + /** * Exclude certain serverside API routes or pages from being instrumented with Sentry. This option takes an array of * strings or regular expressions. diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index b3281b3ac020..0772f3b0b683 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -142,7 +142,7 @@ export function constructWebpackConfigFunction( normalizedAbsoluteResourcePath === middlewareJsPath || normalizedAbsoluteResourcePath === middlewareTsPath ) { - return true; + return userSentryOptions.autoInstrumentMiddleware ?? true; } else { return false; } From b7f551146ee656a7f490420bcc61fbf3a8486e65 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 16 Jan 2023 11:00:45 +0000 Subject: [PATCH 7/8] Rename wrapping target --- packages/nextjs/rollup.npm.config.js | 2 +- packages/nextjs/src/config/loaders/wrappingLoader.ts | 4 ++-- packages/nextjs/src/config/templates/apiWrapperTemplate.ts | 6 +++--- .../src/config/templates/middlewareWrapperTemplate.ts | 6 +++--- packages/nextjs/src/config/templates/pageWrapperTemplate.ts | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/nextjs/rollup.npm.config.js b/packages/nextjs/rollup.npm.config.js index 5268746a7e63..5ef6e1ac96a6 100644 --- a/packages/nextjs/rollup.npm.config.js +++ b/packages/nextjs/rollup.npm.config.js @@ -33,7 +33,7 @@ export default [ // make it so Rollup calms down about the fact that we're combining default and named exports exports: 'named', }, - external: ['@sentry/nextjs', '__SENTRY_WRAPPING_TARGET__'], + external: ['@sentry/nextjs', '__SENTRY_WRAPPING_TARGET_FILE__'], }, }), ), diff --git a/packages/nextjs/src/config/loaders/wrappingLoader.ts b/packages/nextjs/src/config/loaders/wrappingLoader.ts index 5dee8983ef11..a0b9e3e4295f 100644 --- a/packages/nextjs/src/config/loaders/wrappingLoader.ts +++ b/packages/nextjs/src/config/loaders/wrappingLoader.ts @@ -19,7 +19,7 @@ const middlewareWrapperTemplateCode = fs.readFileSync(middlewareWrapperTemplateP const SENTRY_WRAPPER_MODULE_NAME = 'sentry-wrapper-module'; // Needs to end in .cjs in order for the `commonjs` plugin to pick it up -const WRAPPING_TARGET_MODULE_NAME = '__SENTRY_WRAPPING_TARGET__.cjs'; +const WRAPPING_TARGET_MODULE_NAME = '__SENTRY_WRAPPING_TARGET_FILE__.cjs'; type LoaderOptions = { pagesDir: string; @@ -84,7 +84,7 @@ export default function wrappingLoader( templateCode = templateCode.replace(/__ROUTE__/g, parameterizedRoute.replace(/\\/g, '\\\\')); // Replace the import path of the wrapping target in the template with a path that the `wrapUserCode` function will understand. - templateCode = templateCode.replace(/__SENTRY_WRAPPING_TARGET__/g, WRAPPING_TARGET_MODULE_NAME); + templateCode = templateCode.replace(/__SENTRY_WRAPPING_TARGET_FILE__/g, WRAPPING_TARGET_MODULE_NAME); // Run the proxy module code through Rollup, in order to split the `export * from ''` out into // individual exports (which nextjs seems to require). diff --git a/packages/nextjs/src/config/templates/apiWrapperTemplate.ts b/packages/nextjs/src/config/templates/apiWrapperTemplate.ts index 4d06dfb369a0..969d433f126f 100644 --- a/packages/nextjs/src/config/templates/apiWrapperTemplate.ts +++ b/packages/nextjs/src/config/templates/apiWrapperTemplate.ts @@ -2,13 +2,13 @@ * This file is a template for the code which will be substituted when our webpack loader handles API files in the * `pages/` directory. * - * We use `__SENTRY_WRAPPING_TARGET__` as a placeholder for the path to the file being wrapped. Because it's not a real package, + * We use `__SENTRY_WRAPPING_TARGET_FILE__` as a placeholder for the path to the file being wrapped. Because it's not a real package, * this causes both TS and ESLint to complain, hence the pragma comments below. */ // @ts-ignore See above // eslint-disable-next-line import/no-unresolved -import * as origModule from '__SENTRY_WRAPPING_TARGET__'; +import * as origModule from '__SENTRY_WRAPPING_TARGET_FILE__'; // eslint-disable-next-line import/no-extraneous-dependencies import * as Sentry from '@sentry/nextjs'; import type { PageConfig } from 'next'; @@ -60,4 +60,4 @@ export default userProvidedHandler ? Sentry.withSentryAPI(userProvidedHandler, ' // not include anything whose name matchs something we've explicitly exported above. // @ts-ignore See above // eslint-disable-next-line import/no-unresolved -export * from '__SENTRY_WRAPPING_TARGET__'; +export * from '__SENTRY_WRAPPING_TARGET_FILE__'; diff --git a/packages/nextjs/src/config/templates/middlewareWrapperTemplate.ts b/packages/nextjs/src/config/templates/middlewareWrapperTemplate.ts index 5bca0012ab5d..373f63646933 100644 --- a/packages/nextjs/src/config/templates/middlewareWrapperTemplate.ts +++ b/packages/nextjs/src/config/templates/middlewareWrapperTemplate.ts @@ -1,13 +1,13 @@ /* * This file is a template for the code which will be substituted when our webpack loader handles middleware files. * - * We use `__SENTRY_WRAPPING_TARGET__` as a placeholder for the path to the file being wrapped. Because it's not a real package, + * We use `__SENTRY_WRAPPING_TARGET_FILE__` as a placeholder for the path to the file being wrapped. Because it's not a real package, * this causes both TS and ESLint to complain, hence the pragma comments below. */ // @ts-ignore See above // eslint-disable-next-line import/no-unresolved -import * as origModule from '__SENTRY_WRAPPING_TARGET__'; +import * as origModule from '__SENTRY_WRAPPING_TARGET_FILE__'; // eslint-disable-next-line import/no-extraneous-dependencies import * as Sentry from '@sentry/nextjs'; @@ -47,4 +47,4 @@ export default userProvidedDefaultHandler ? Sentry.withSentryMiddleware(userProv // not include anything whose name matchs something we've explicitly exported above. // @ts-ignore See above // eslint-disable-next-line import/no-unresolved -export * from '__SENTRY_WRAPPING_TARGET__'; +export * from '__SENTRY_WRAPPING_TARGET_FILE__'; diff --git a/packages/nextjs/src/config/templates/pageWrapperTemplate.ts b/packages/nextjs/src/config/templates/pageWrapperTemplate.ts index db8385c97f81..955e920552d1 100644 --- a/packages/nextjs/src/config/templates/pageWrapperTemplate.ts +++ b/packages/nextjs/src/config/templates/pageWrapperTemplate.ts @@ -2,13 +2,13 @@ * This file is a template for the code which will be substituted when our webpack loader handles non-API files in the * `pages/` directory. * - * We use `__SENTRY_WRAPPING_TARGET__` as a placeholder for the path to the file being wrapped. Because it's not a real package, + * We use `__SENTRY_WRAPPING_TARGET_FILE__` as a placeholder for the path to the file being wrapped. Because it's not a real package, * this causes both TS and ESLint to complain, hence the pragma comments below. */ // @ts-ignore See above // eslint-disable-next-line import/no-unresolved -import * as wrapee from '__SENTRY_WRAPPING_TARGET__'; +import * as wrapee from '__SENTRY_WRAPPING_TARGET_FILE__'; // eslint-disable-next-line import/no-extraneous-dependencies import * as Sentry from '@sentry/nextjs'; import type { GetServerSideProps, GetStaticProps, NextPage as NextPageComponent } from 'next'; @@ -54,4 +54,4 @@ export default pageComponent; // not include anything whose name matchs something we've explicitly exported above. // @ts-ignore See above // eslint-disable-next-line import/no-unresolved -export * from '__SENTRY_WRAPPING_TARGET__'; +export * from '__SENTRY_WRAPPING_TARGET_FILE__'; From 26f154dd421c374a59871aaac6070e48cd6f721d Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 16 Jan 2023 11:43:31 +0000 Subject: [PATCH 8/8] Remove unused loader option --- packages/nextjs/src/config/loaders/wrappingLoader.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/nextjs/src/config/loaders/wrappingLoader.ts b/packages/nextjs/src/config/loaders/wrappingLoader.ts index a0b9e3e4295f..8178ace4c096 100644 --- a/packages/nextjs/src/config/loaders/wrappingLoader.ts +++ b/packages/nextjs/src/config/loaders/wrappingLoader.ts @@ -25,7 +25,6 @@ type LoaderOptions = { pagesDir: string; pageExtensionRegex: string; excludeServerRoutes: Array; - isEdgeRuntime: boolean; }; /**