Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(nextjs): Handle CJS API route exports #5865

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 21 additions & 6 deletions packages/nextjs/src/config/templates/apiProxyLoaderTemplate.ts
Expand Up @@ -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
Expand All @@ -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.
Expand Down
@@ -0,0 +1,7 @@
import { NextApiRequest, NextApiResponse } from 'next';

const handler = async (_req: NextApiRequest, res: NextApiResponse): Promise<void> => {
res.status(200).json({ success: true });
};

module.exports = handler;
@@ -0,0 +1,7 @@
import { NextApiRequest, NextApiResponse } from 'next';

const handler = async (_req: NextApiRequest, res: NextApiResponse): Promise<void> => {
res.status(200).json({ success: true });
};

module.exports = handler;
55 changes: 55 additions & 0 deletions 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');
};