@@ -34,18 +34,31 @@ interface Server {
3434 publicDir : string ;
3535}
3636
37- export interface NextRequest extends http . IncomingMessage {
37+ export type NextRequest = (
38+ | http . IncomingMessage // in nextjs versions < 12.0.9, `NextRequest` extends `http.IncomingMessage`
39+ | {
40+ _req : http . IncomingMessage ; // in nextjs versions >= 12.0.9, `NextRequest` wraps `http.IncomingMessage`
41+ }
42+ ) & {
3843 cookies : Record < string , string > ;
3944 url : string ;
4045 query : { [ key : string ] : string } ;
4146 headers : { [ key : string ] : string } ;
4247 body : string | { [ key : string ] : unknown } ;
43- }
44- type NextResponse = http . ServerResponse ;
48+ method : string ;
49+ } ;
50+
51+ type NextResponse =
52+ // in nextjs versions < 12.0.9, `NextResponse` extends `http.ServerResponse`
53+ | http . ServerResponse
54+ // in nextjs versions >= 12.0.9, `NextResponse` wraps `http.ServerResponse`
55+ | {
56+ _res : http . ServerResponse ;
57+ } ;
4558
4659// the methods we'll wrap
4760type HandlerGetter = ( ) => Promise < ReqHandler > ;
48- type ReqHandler = ( req : NextRequest , res : NextResponse , parsedUrl ?: url . UrlWithParsedQuery ) => Promise < void > ;
61+ type ReqHandler = ( nextReq : NextRequest , nextRes : NextResponse , parsedUrl ?: url . UrlWithParsedQuery ) => Promise < void > ;
4962type ErrorLogger = ( err : Error ) => void ;
5063type ApiPageEnsurer = ( path : string ) => Promise < void > ;
5164type PageComponentFinder = (
@@ -205,10 +218,16 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
205218 // add transaction start and stop to the normal request handling
206219 const wrappedReqHandler = async function (
207220 this : Server ,
208- req : NextRequest ,
209- res : NextResponse ,
221+ nextReq : NextRequest ,
222+ nextRes : NextResponse ,
210223 parsedUrl ?: url . UrlWithParsedQuery ,
211224 ) : Promise < void > {
225+ // Starting with version 12.0.9, nextjs wraps the incoming request in a `NodeNextRequest` object and the outgoing
226+ // response in a `NodeNextResponse` object before passing them to the handler. (This is necessary here but not in
227+ // `withSentry` because by the time nextjs passes them to an API handler, it's unwrapped them again.)
228+ const req = '_req' in nextReq ? nextReq . _req : nextReq ;
229+ const res = '_res' in nextRes ? nextRes . _res : nextRes ;
230+
212231 // wrap everything in a domain in order to prevent scope bleed between requests
213232 const local = domain . create ( ) ;
214233 local . add ( req ) ;
@@ -220,23 +239,23 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
220239 const currentScope = getCurrentHub ( ) . getScope ( ) ;
221240
222241 if ( currentScope ) {
223- currentScope . addEventProcessor ( event => parseRequest ( event , req ) ) ;
242+ currentScope . addEventProcessor ( event => parseRequest ( event , nextReq ) ) ;
224243
225244 // We only want to record page and API requests
226- if ( hasTracingEnabled ( ) && shouldTraceRequest ( req . url , publicDirFiles ) ) {
245+ if ( hasTracingEnabled ( ) && shouldTraceRequest ( nextReq . url , publicDirFiles ) ) {
227246 // If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision)
228247 let traceparentData ;
229- if ( req . headers && isString ( req . headers [ 'sentry-trace' ] ) ) {
230- traceparentData = extractTraceparentData ( req . headers [ 'sentry-trace' ] as string ) ;
248+ if ( nextReq . headers && isString ( nextReq . headers [ 'sentry-trace' ] ) ) {
249+ traceparentData = extractTraceparentData ( nextReq . headers [ 'sentry-trace' ] as string ) ;
231250 logger . log ( `[Tracing] Continuing trace ${ traceparentData ?. traceId } .` ) ;
232251 }
233252
234253 // pull off query string, if any
235- const reqPath = stripUrlQueryAndFragment ( req . url ) ;
254+ const reqPath = stripUrlQueryAndFragment ( nextReq . url ) ;
236255
237256 // requests for pages will only ever be GET requests, so don't bother to include the method in the transaction
238257 // name; requests to API routes could be GET, POST, PUT, etc, so do include it there
239- const namePrefix = req . url . startsWith ( '/api' ) ? `${ ( req . method || 'GET' ) . toUpperCase ( ) } ` : '' ;
258+ const namePrefix = nextReq . url . startsWith ( '/api' ) ? `${ nextReq . method . toUpperCase ( ) } ` : '' ;
240259
241260 const transaction = startTransaction (
242261 {
@@ -245,8 +264,12 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
245264 metadata : { requestPath : reqPath } ,
246265 ...traceparentData ,
247266 } ,
248- // extra context passed to the `tracesSampler`
249- { request : req } ,
267+ // Extra context passed to the `tracesSampler` (Note: We're combining `nextReq` and `req` this way in order
268+ // to not break people's `tracesSampler` functions, even though the format of `nextReq` has changed (see
269+ // note above re: nextjs 12.0.9). If `nextReq === req` (pre 12.0.9), then spreading `req` is a no-op - we're
270+ // just spreading the same stuff twice. If `nextReq` contains `req` (12.0.9 and later), then spreading `req`
271+ // mimics the old format by flattening the data.)
272+ { request : { ...nextReq , ...req } } ,
250273 ) ;
251274
252275 currentScope . setSpan ( transaction ) ;
@@ -270,7 +293,7 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
270293 }
271294 }
272295
273- return origReqHandler . call ( this , req , res , parsedUrl ) ;
296+ return origReqHandler . call ( this , nextReq , nextRes , parsedUrl ) ;
274297 } ) ;
275298 } ;
276299
0 commit comments