Skip to content

Commit e82be42

Browse files
author
Luca Forstner
authored
fix(nextjs): Don't accidentally trigger static generation bailout (#9749)
1 parent 0f117e7 commit e82be42

File tree

5 files changed

+33
-19
lines changed

5 files changed

+33
-19
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { NextResponse } from 'next/server';
2+
3+
export async function GET() {
4+
return NextResponse.json({ result: 'static response' });
5+
}
6+
7+
// This export makes it so that this route is always dynamically rendered (i.e Sentry will trace)
8+
export const revalidate = 0;
9+
10+
// This export makes it so that this route will throw an error if the Request object is accessed in some way.
11+
export const dynamic = 'error';

packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,10 @@ test.describe('Edge runtime', () => {
9494
expect(routehandlerError.contexts?.runtime?.name).toBe('vercel-edge');
9595
});
9696
});
97+
98+
test('should not crash route handlers that are configured with `export const dynamic = "error"`', async ({
99+
request,
100+
}) => {
101+
const response = await request.get('/route-handlers/static');
102+
expect(await response.json()).toStrictEqual({ result: 'static response' });
103+
});

packages/nextjs/src/common/types.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,19 @@ export type ServerComponentContext = {
1818
};
1919

2020
export interface RouteHandlerContext {
21-
// TODO(v8): Remove
22-
/**
23-
* @deprecated The SDK will automatically pick up the method from the incoming Request object instead.
24-
*/
2521
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
2622
parameterizedRoute: string;
2723
// TODO(v8): Remove
2824
/**
29-
* @deprecated The SDK will automatically pick up the `sentry-trace` header from the incoming Request object instead.
25+
* @deprecated pass a complete `Headers` object with the `headers` field instead.
3026
*/
3127
sentryTraceHeader?: string;
3228
// TODO(v8): Remove
3329
/**
34-
* @deprecated The SDK will automatically pick up the `baggage` header from the incoming Request object instead.
30+
* @deprecated pass a complete `Headers` object with the `headers` field instead.
3531
*/
3632
baggageHeader?: string;
33+
headers?: WebFetchHeaders;
3734
}
3835

3936
export type VercelCronsConfig = { path?: string; schedule?: string }[] | undefined;

packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { addTracingExtensions, captureException, flush, getCurrentHub, runWithAsyncContext, trace } from '@sentry/core';
2-
import { tracingContextFromHeaders, winterCGRequestToRequestData } from '@sentry/utils';
2+
import { tracingContextFromHeaders, winterCGHeadersToDict } from '@sentry/utils';
33

44
import { isRedirectNavigationError } from './nextNavigationErrorUtils';
55
import type { RouteHandlerContext } from './types';
@@ -15,23 +15,16 @@ export function wrapRouteHandlerWithSentry<F extends (...args: any[]) => any>(
1515
): (...args: Parameters<F>) => ReturnType<F> extends Promise<unknown> ? ReturnType<F> : Promise<ReturnType<F>> {
1616
addTracingExtensions();
1717
// eslint-disable-next-line deprecation/deprecation
18-
const { method, parameterizedRoute, baggageHeader, sentryTraceHeader } = context;
18+
const { method, parameterizedRoute, baggageHeader, sentryTraceHeader, headers } = context;
1919
return new Proxy(routeHandler, {
2020
apply: (originalFunction, thisArg, args) => {
2121
return runWithAsyncContext(async () => {
2222
const hub = getCurrentHub();
2323
const currentScope = hub.getScope();
2424

25-
let req: Request | undefined;
26-
let reqMethod: string | undefined;
27-
if (args[0] instanceof Request) {
28-
req = args[0];
29-
reqMethod = req.method;
30-
}
31-
3225
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
33-
sentryTraceHeader,
34-
baggageHeader,
26+
sentryTraceHeader ?? headers?.get('sentry-trace') ?? undefined,
27+
baggageHeader ?? headers?.get('baggage'),
3528
);
3629
currentScope.setPropagationContext(propagationContext);
3730

@@ -40,11 +33,13 @@ export function wrapRouteHandlerWithSentry<F extends (...args: any[]) => any>(
4033
res = await trace(
4134
{
4235
op: 'http.server',
43-
name: `${reqMethod ?? method} ${parameterizedRoute}`,
36+
name: `${method} ${parameterizedRoute}`,
4437
status: 'ok',
4538
...traceparentData,
4639
metadata: {
47-
request: req ? winterCGRequestToRequestData(req) : undefined,
40+
request: {
41+
headers: headers ? winterCGHeadersToDict(headers) : undefined,
42+
},
4843
source: 'route',
4944
dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
5045
},

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as Sentry from '@sentry/nextjs';
2+
import type { WebFetchHeaders } from '@sentry/types';
23
// @ts-expect-error Because we cannot be sure if the RequestAsyncStorage module exists (it is not part of the Next.js public
34
// API) we use a shim if it doesn't exist. The logic for this is in the wrapping loader.
45
import { requestAsyncStorage } from '__SENTRY_NEXTJS_REQUEST_ASYNC_STORAGE_SHIM__';
@@ -34,12 +35,14 @@ function wrapHandler<T>(handler: T, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | '
3435
apply: (originalFunction, thisArg, args) => {
3536
let sentryTraceHeader: string | undefined | null = undefined;
3637
let baggageHeader: string | undefined | null = undefined;
38+
let headers: WebFetchHeaders | undefined = undefined;
3739

3840
// We try-catch here just in case the API around `requestAsyncStorage` changes unexpectedly since it is not public API
3941
try {
4042
const requestAsyncStore = requestAsyncStorage.getStore();
4143
sentryTraceHeader = requestAsyncStore?.headers.get('sentry-trace') ?? undefined;
4244
baggageHeader = requestAsyncStore?.headers.get('baggage') ?? undefined;
45+
headers = requestAsyncStore?.headers;
4346
} catch (e) {
4447
/** empty */
4548
}
@@ -50,6 +53,7 @@ function wrapHandler<T>(handler: T, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | '
5053
parameterizedRoute: '__ROUTE__',
5154
sentryTraceHeader,
5255
baggageHeader,
56+
headers,
5357
}).apply(thisArg, args);
5458
},
5559
});

0 commit comments

Comments
 (0)