Skip to content
Closed
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
6 changes: 3 additions & 3 deletions packages/react-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@
"glob": "^13.0.6"
},
"devDependencies": {
"@react-router/dev": "^7.17.0",
"@react-router/node": "^7.15.0",
"@react-router/dev": "^8.3.0",
"@react-router/node": "^8.3.0",
"react": "^18.3.1",
"react-router": "^7.18.0",
"react-router": "^8.3.0",
"vite": "^6.4.3"
},
"peerDependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { createReadableStreamFromReadable } from '@react-router/node';
import type { ReactNode } from 'react';
import React from 'react';
import type { AppLoadContext, EntryContext, RouterContextProvider, ServerRouter } from 'react-router';
import type { EntryContext, RouterContextProvider, ServerRouter } from 'react-router';
import { PassThrough } from 'stream';
import { getMetaTagTransformer } from './getMetaTagTransformer';
import { wrapSentryHandleRequest } from './wrapSentryHandleRequest';
Expand Down Expand Up @@ -53,6 +53,9 @@ export interface SentryHandleRequestOptions {
botRegex?: RegExp;
}

// react-router v8 removed `AppLoadContext`. The SDK still supports v7, so mirror the v7 shape here.
type AppLoadContext = Record<string, unknown>;

type HandleRequestWithoutMiddleware = (
request: Request,
responseStatusCode: number,
Expand Down Expand Up @@ -145,5 +148,5 @@ export function createSentryHandleRequest(
};

// Wrap the handle request function for request parametrization
return wrapSentryHandleRequest(handleRequest) as HandleRequestWithoutMiddleware & HandleRequestWithMiddleware;
return wrapSentryHandleRequest(handleRequest);
}
74 changes: 11 additions & 63 deletions packages/react-router/src/server/wrapSentryHandleRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,58 +7,35 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
updateSpanName,
} from '@sentry/core';
import type { AppLoadContext, EntryContext, RouterContextProvider } from 'react-router';
import type { EntryContext } from 'react-router';
import { isInstrumentationApiUsed } from './serverGlobals';

type OriginalHandleRequestWithoutMiddleware = (
// The load context is `AppLoadContext` on react-router v7 and `RouterContextProvider` on v8, and apps
// can extend `AppLoadContext` with declaration merging. Inference keeps the wrapper compatible with
// all of these shapes.
type OriginalHandleRequest<LoadContext> = (
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
routerContext: EntryContext,
loadContext: AppLoadContext,
loadContext: LoadContext,
) => Promise<unknown>;

type OriginalHandleRequestWithMiddleware = (
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
routerContext: EntryContext,
loadContext: RouterContextProvider,
) => Promise<unknown>;

/**
* Wraps the original handleRequest function to add Sentry instrumentation.
*
* @param originalHandle - The original handleRequest function to wrap
* @returns A wrapped version of the handle request function with Sentry instrumentation
*/
export function wrapSentryHandleRequest(
originalHandle: OriginalHandleRequestWithoutMiddleware,
): OriginalHandleRequestWithoutMiddleware;
/**
* Wraps the original handleRequest function to add Sentry instrumentation.
*
* @param originalHandle - The original handleRequest function to wrap
* @returns A wrapped version of the handle request function with Sentry instrumentation
*/
export function wrapSentryHandleRequest(
originalHandle: OriginalHandleRequestWithMiddleware,
): OriginalHandleRequestWithMiddleware;
/**
* Wraps the original handleRequest function to add Sentry instrumentation.
*
* @param originalHandle - The original handleRequest function to wrap
* @returns A wrapped version of the handle request function with Sentry instrumentation
*/
export function wrapSentryHandleRequest(
originalHandle: OriginalHandleRequestWithoutMiddleware | OriginalHandleRequestWithMiddleware,
): OriginalHandleRequestWithoutMiddleware | OriginalHandleRequestWithMiddleware {
export function wrapSentryHandleRequest<LoadContext>(
originalHandle: OriginalHandleRequest<LoadContext>,
): OriginalHandleRequest<LoadContext> {
return async function sentryInstrumentedHandleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
routerContext: EntryContext,
loadContext: AppLoadContext | RouterContextProvider,
loadContext: LoadContext,
) {
const parameterizedPath =
routerContext?.staticHandlerContext?.matches?.[routerContext.staticHandlerContext.matches.length - 1]?.route.path;
Expand Down Expand Up @@ -92,38 +69,9 @@ export function wrapSentryHandleRequest(
}

try {
// Type guard to call the correct overload based on loadContext type
if (isRouterContextProvider(loadContext)) {
// loadContext is RouterContextProvider
return await (originalHandle as OriginalHandleRequestWithMiddleware)(
request,
responseStatusCode,
responseHeaders,
routerContext,
loadContext,
);
} else {
// loadContext is AppLoadContext
return await (originalHandle as OriginalHandleRequestWithoutMiddleware)(
request,
responseStatusCode,
responseHeaders,
routerContext,
loadContext,
);
}
return await originalHandle(request, responseStatusCode, responseHeaders, routerContext, loadContext);
} finally {
await flushIfServerless();
}

/**
* Helper type guard to determine if the context is a RouterContextProvider.
*
* @param ctx - The context to check
* @returns True if the context is a RouterContextProvider
*/
function isRouterContextProvider(ctx: AppLoadContext | RouterContextProvider): ctx is RouterContextProvider {
return typeof (ctx as RouterContextProvider)?.get === 'function';
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,7 @@ describe('createSentryHandleRequest', () => {
manifestPath: '/path/to/manifest',
},
routeModules: {},
future: {
v8_middleware: false,
v8_passThroughRequests: false,
v8_trailingSlashAwareDataRequests: false,
},
future: {},
isSpaMode: false,
branches: [],
staticHandlerContext: {
Expand Down
Loading