Skip to content

Commit b7c0e54

Browse files
authored
fix(nextjs): Handle CJS API route exports (#5865)
1 parent 92dd0ed commit b7c0e54

File tree

4 files changed

+90
-6
lines changed

4 files changed

+90
-6
lines changed

packages/nextjs/src/config/templates/apiProxyLoaderTemplate.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,29 @@ import type { PageConfig } from 'next';
1616
// multiple versions of next. See note in `wrappers/types` for more.
1717
import type { NextApiHandler } from '../wrappers';
1818

19-
type NextApiModule = {
20-
default: NextApiHandler;
21-
config?: PageConfig;
22-
};
19+
type NextApiModule = (
20+
| {
21+
// ESM export
22+
default?: NextApiHandler;
23+
}
24+
// CJS export
25+
| NextApiHandler
26+
) & { config?: PageConfig };
2327

2428
const userApiModule = origModule as NextApiModule;
2529

26-
const maybeWrappedHandler = userApiModule.default;
30+
// Default to undefined. It's possible for Next.js users to not define any exports/handlers in an API route. If that is
31+
// the case Next.js wil crash during runtime but the Sentry SDK should definitely not crash so we need tohandle it.
32+
let userProvidedHandler = undefined;
33+
34+
if ('default' in userApiModule && typeof userApiModule.default === 'function') {
35+
// Handle when user defines via ESM export: `export default myFunction;`
36+
userProvidedHandler = userApiModule.default;
37+
} else if (typeof userApiModule === 'function') {
38+
// Handle when user defines via CJS export: "module.exports = myFunction;"
39+
userProvidedHandler = userApiModule;
40+
}
41+
2742
const origConfig = userApiModule.config || {};
2843

2944
// Setting `externalResolver` to `true` prevents nextjs from throwing a warning in dev about API routes resolving
@@ -38,7 +53,7 @@ export const config = {
3853
},
3954
};
4055

41-
export default Sentry.withSentryAPI(maybeWrappedHandler, '__ROUTE__');
56+
export default userProvidedHandler ? Sentry.withSentryAPI(userProvidedHandler, '__ROUTE__') : undefined;
4257

4358
// Re-export anything exported by the page module we're wrapping. When processing this code, Rollup is smart enough to
4459
// not include anything whose name matchs something we've explicitly exported above.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { NextApiRequest, NextApiResponse } from 'next';
2+
3+
const handler = async (_req: NextApiRequest, res: NextApiResponse): Promise<void> => {
4+
res.status(200).json({ success: true });
5+
};
6+
7+
module.exports = handler;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { NextApiRequest, NextApiResponse } from 'next';
2+
3+
const handler = async (_req: NextApiRequest, res: NextApiResponse): Promise<void> => {
4+
res.status(200).json({ success: true });
5+
};
6+
7+
module.exports = handler;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
const assert = require('assert');
2+
3+
const { sleep } = require('../utils/common');
4+
const { getAsync, interceptTracingRequest } = require('../utils/server');
5+
6+
module.exports = async ({ url: urlBase, argv }) => {
7+
const unwrappedRoute = '/api/withSentryAPI/unwrapped/cjsExport';
8+
const interceptedUnwrappedRequest = interceptTracingRequest(
9+
{
10+
contexts: {
11+
trace: {
12+
op: 'http.server',
13+
status: 'ok',
14+
tags: { 'http.status_code': '200' },
15+
},
16+
},
17+
transaction: `GET ${unwrappedRoute}`,
18+
type: 'transaction',
19+
request: {
20+
url: `${urlBase}${unwrappedRoute}`,
21+
},
22+
},
23+
argv,
24+
'unwrapped CJS route',
25+
);
26+
const responseUnwrapped = await getAsync(`${urlBase}${unwrappedRoute}`);
27+
assert.equal(responseUnwrapped, '{"success":true}');
28+
29+
const wrappedRoute = '/api/withSentryAPI/wrapped/cjsExport';
30+
const interceptedWrappedRequest = interceptTracingRequest(
31+
{
32+
contexts: {
33+
trace: {
34+
op: 'http.server',
35+
status: 'ok',
36+
tags: { 'http.status_code': '200' },
37+
},
38+
},
39+
transaction: `GET ${wrappedRoute}`,
40+
type: 'transaction',
41+
request: {
42+
url: `${urlBase}${wrappedRoute}`,
43+
},
44+
},
45+
argv,
46+
'wrapped CJS route',
47+
);
48+
const responseWrapped = await getAsync(`${urlBase}${wrappedRoute}`);
49+
assert.equal(responseWrapped, '{"success":true}');
50+
51+
await sleep(250);
52+
53+
assert.ok(interceptedUnwrappedRequest.isDone(), 'Did not intercept unwrapped request');
54+
assert.ok(interceptedWrappedRequest.isDone(), 'Did not intercept wrapped request');
55+
};

0 commit comments

Comments
 (0)