diff --git a/packages/nextjs/src/config/templates/apiProxyLoaderTemplate.ts b/packages/nextjs/src/config/templates/apiProxyLoaderTemplate.ts index 52bd696ece16..1cd7c40181eb 100644 --- a/packages/nextjs/src/config/templates/apiProxyLoaderTemplate.ts +++ b/packages/nextjs/src/config/templates/apiProxyLoaderTemplate.ts @@ -16,14 +16,29 @@ import type { PageConfig } from 'next'; // multiple versions of next. See note in `wrappers/types` for more. import type { NextApiHandler } from '../wrappers'; -type NextApiModule = { - default: NextApiHandler; - config?: PageConfig; -}; +type NextApiModule = ( + | { + // ESM export + default?: NextApiHandler; + } + // CJS export + | NextApiHandler +) & { config?: PageConfig }; const userApiModule = origModule as NextApiModule; -const maybeWrappedHandler = userApiModule.default; +// 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 userProvidedHandler = undefined; + +if ('default' in userApiModule && typeof userApiModule.default === 'function') { + // Handle when user defines via ESM export: `export default myFunction;` + userProvidedHandler = userApiModule.default; +} else if (typeof userApiModule === 'function') { + // Handle when user defines via CJS export: "module.exports = myFunction;" + userProvidedHandler = userApiModule; +} + const origConfig = userApiModule.config || {}; // Setting `externalResolver` to `true` prevents nextjs from throwing a warning in dev about API routes resolving @@ -38,7 +53,7 @@ export const config = { }, }; -export default Sentry.withSentryAPI(maybeWrappedHandler, '__ROUTE__'); +export default userProvidedHandler ? Sentry.withSentryAPI(userProvidedHandler, '__ROUTE__') : 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. diff --git a/packages/nextjs/test/integration/pages/api/withSentryAPI/unwrapped/cjsExport.ts b/packages/nextjs/test/integration/pages/api/withSentryAPI/unwrapped/cjsExport.ts new file mode 100644 index 000000000000..6ae521fa5cb4 --- /dev/null +++ b/packages/nextjs/test/integration/pages/api/withSentryAPI/unwrapped/cjsExport.ts @@ -0,0 +1,7 @@ +import { NextApiRequest, NextApiResponse } from 'next'; + +const handler = async (_req: NextApiRequest, res: NextApiResponse): Promise => { + res.status(200).json({ success: true }); +}; + +module.exports = handler; diff --git a/packages/nextjs/test/integration/pages/api/withSentryAPI/wrapped/cjsExport.ts b/packages/nextjs/test/integration/pages/api/withSentryAPI/wrapped/cjsExport.ts new file mode 100644 index 000000000000..6ae521fa5cb4 --- /dev/null +++ b/packages/nextjs/test/integration/pages/api/withSentryAPI/wrapped/cjsExport.ts @@ -0,0 +1,7 @@ +import { NextApiRequest, NextApiResponse } from 'next'; + +const handler = async (_req: NextApiRequest, res: NextApiResponse): Promise => { + res.status(200).json({ success: true }); +}; + +module.exports = handler; diff --git a/packages/nextjs/test/integration/test/server/cjsApiEndpoints.js b/packages/nextjs/test/integration/test/server/cjsApiEndpoints.js new file mode 100644 index 000000000000..e154599fcbd7 --- /dev/null +++ b/packages/nextjs/test/integration/test/server/cjsApiEndpoints.js @@ -0,0 +1,55 @@ +const assert = require('assert'); + +const { sleep } = require('../utils/common'); +const { getAsync, interceptTracingRequest } = require('../utils/server'); + +module.exports = async ({ url: urlBase, argv }) => { + const unwrappedRoute = '/api/withSentryAPI/unwrapped/cjsExport'; + const interceptedUnwrappedRequest = interceptTracingRequest( + { + contexts: { + trace: { + op: 'http.server', + status: 'ok', + tags: { 'http.status_code': '200' }, + }, + }, + transaction: `GET ${unwrappedRoute}`, + type: 'transaction', + request: { + url: `${urlBase}${unwrappedRoute}`, + }, + }, + argv, + 'unwrapped CJS route', + ); + const responseUnwrapped = await getAsync(`${urlBase}${unwrappedRoute}`); + assert.equal(responseUnwrapped, '{"success":true}'); + + const wrappedRoute = '/api/withSentryAPI/wrapped/cjsExport'; + const interceptedWrappedRequest = interceptTracingRequest( + { + contexts: { + trace: { + op: 'http.server', + status: 'ok', + tags: { 'http.status_code': '200' }, + }, + }, + transaction: `GET ${wrappedRoute}`, + type: 'transaction', + request: { + url: `${urlBase}${wrappedRoute}`, + }, + }, + argv, + 'wrapped CJS route', + ); + const responseWrapped = await getAsync(`${urlBase}${wrappedRoute}`); + assert.equal(responseWrapped, '{"success":true}'); + + await sleep(250); + + assert.ok(interceptedUnwrappedRequest.isDone(), 'Did not intercept unwrapped request'); + assert.ok(interceptedWrappedRequest.isDone(), 'Did not intercept wrapped request'); +};