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): Detect new locations for request async storage to support Next.js v15.0.0-canary.180 and higher #13920

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@types/node": "18.11.17",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.9",
"next": "15.0.0-canary.112",
"next": "15.0.0-canary.182",
"react": "beta",
"react-dom": "beta",
"typescript": "4.9.5"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,29 @@ import * as Sentry from '@sentry/nextjs';
import type { WebFetchHeaders } from '@sentry/types';
// @ts-expect-error Because we cannot be sure if the RequestAsyncStorage module exists (it is not part of the Next.js public
// API) we use a shim if it doesn't exist. The logic for this is in the wrapping loader.
import { requestAsyncStorage } from '__SENTRY_NEXTJS_REQUEST_ASYNC_STORAGE_SHIM__';
import * as origModule from '__SENTRY_NEXTJS_REQUEST_ASYNC_STORAGE_SHIM__';
// @ts-expect-error See above
import * as routeModule from '__SENTRY_WRAPPING_TARGET_FILE__';

import type { RequestAsyncStorage } from './requestAsyncStorageShim';

type NextAsyncStorageModule =
| {
workUnitAsyncStorage: RequestAsyncStorage;
}
| {
requestAsyncStorage: RequestAsyncStorage;
};

const asyncStorageModule = { ...origModule } as NextAsyncStorageModule;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you find a reason that we should clone the object? It may be a bit expensive to do so in various ways.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, requestAsyncStorage is not exported is returned when not use clone.


const requestAsyncStorage: RequestAsyncStorage | undefined =
'workUnitAsyncStorage' in asyncStorageModule
? asyncStorageModule.workUnitAsyncStorage
: 'requestAsyncStorage' in asyncStorageModule
? asyncStorageModule.requestAsyncStorage
: undefined;

function wrapHandler<T>(handler: T, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'): T {
// Running the instrumentation code during the build phase will mark any function as "dynamic" because we're accessing
// the Request object. We do not want to turn handlers dynamic so we skip instrumentation in the build phase.
Expand All @@ -28,7 +45,7 @@ function wrapHandler<T>(handler: T, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | '
// We try-catch here just in case the API around `requestAsyncStorage` changes unexpectedly since it is not public API
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const requestAsyncStore = requestAsyncStorage.getStore() as ReturnType<RequestAsyncStorage['getStore']>;
const requestAsyncStore = requestAsyncStorage?.getStore() as ReturnType<RequestAsyncStorage['getStore']>;
sentryTraceHeader = requestAsyncStore?.headers.get('sentry-trace') ?? undefined;
baggageHeader = requestAsyncStore?.headers.get('baggage') ?? undefined;
headers = requestAsyncStore?.headers;
Expand All @@ -54,8 +71,6 @@ export * from '__SENTRY_WRAPPING_TARGET_FILE__';
// @ts-expect-error This is the file we're wrapping
export { default } from '__SENTRY_WRAPPING_TARGET_FILE__';

declare const requestAsyncStorage: RequestAsyncStorage;

type RouteHandler = (...args: unknown[]) => unknown;

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,29 @@ import * as Sentry from '@sentry/nextjs';
import type { WebFetchHeaders } from '@sentry/types';
// @ts-expect-error Because we cannot be sure if the RequestAsyncStorage module exists (it is not part of the Next.js public
// API) we use a shim if it doesn't exist. The logic for this is in the wrapping loader.
// biome-ignore lint/nursery/noUnusedImports: Biome doesn't understand the shim with variable import path
import { requestAsyncStorage } from '__SENTRY_NEXTJS_REQUEST_ASYNC_STORAGE_SHIM__';
import * as origModule from '__SENTRY_NEXTJS_REQUEST_ASYNC_STORAGE_SHIM__';
// @ts-expect-error We use `__SENTRY_WRAPPING_TARGET_FILE__` as a placeholder for the path to the file being wrapped.
// biome-ignore lint/nursery/noUnusedImports: Biome doesn't understand the shim with variable import path
import * as serverComponentModule from '__SENTRY_WRAPPING_TARGET_FILE__';

import type { RequestAsyncStorage } from './requestAsyncStorageShim';

declare const requestAsyncStorage: RequestAsyncStorage;
type NextAsyncStorageModule =
| {
workUnitAsyncStorage: RequestAsyncStorage;
}
| {
requestAsyncStorage: RequestAsyncStorage;
};

const asyncStorageModule = { ...origModule } as NextAsyncStorageModule;

const requestAsyncStorage: RequestAsyncStorage | undefined =
'workUnitAsyncStorage' in asyncStorageModule
? asyncStorageModule.workUnitAsyncStorage
: 'requestAsyncStorage' in asyncStorageModule
? asyncStorageModule.requestAsyncStorage
: undefined;

declare const serverComponentModule: {
default: unknown;
Expand All @@ -34,7 +48,8 @@ if (typeof serverComponent === 'function') {

// We try-catch here just in `requestAsyncStorage` is undefined since it may not be defined
try {
const requestAsyncStore = requestAsyncStorage.getStore();
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const requestAsyncStore = requestAsyncStorage?.getStore() as ReturnType<RequestAsyncStorage['getStore']>;
sentryTraceHeader = requestAsyncStore?.headers.get('sentry-trace') ?? undefined;
baggageHeader = requestAsyncStore?.headers.get('baggage') ?? undefined;
headers = requestAsyncStore?.headers;
Expand Down
6 changes: 6 additions & 0 deletions packages/nextjs/src/config/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,12 @@ const POTENTIAL_REQUEST_ASYNC_STORAGE_LOCATIONS = [
// Introduced in Next.js 13.4.20
// https://github.com/vercel/next.js/blob/e1bc270830f2fc2df3542d4ef4c61b916c802df3/packages/next/src/client/components/request-async-storage.external.ts
'next/dist/client/components/request-async-storage.external.js',
// Introduced in Next.js 15.0.0-canary.180
// https://github.com/vercel/next.js/blob/541167b9b0fed6af9f36472e632863ffec41f18c/packages/next/src/server/app-render/work-unit-async-storage.external.ts
'next/dist/server/app-render/work-unit-async-storage.external.js',
// Introduced in Next.js 15.0.0-canary.182
// https://github.com/vercel/next.js/blob/f35159e5e80138ca7373f57b47edcaae3bcf1728/packages/next/src/client/components/work-unit-async-storage.external.ts
'next/dist/client/components/work-unit-async-storage.external.js',
];

function getRequestAsyncStorageModuleLocation(
Expand Down
Loading