Skip to content

Commit 66f9d56

Browse files
chargomeclaude
andauthored
feat(nextjs)!: Use middleware span op for Next.js middleware (#22674)
Aligns the Next.js middleware span op with the `@sentry/conventions` `middleware`, replacing the previous `http.server.middleware`. The duplicate middleware span this issue also described was already resolved separately in #18456 (the build-time wrapper no longer starts its own span); this PR adds the regression test coverage that was missing there. closes #22636 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 671d333 commit 66f9d56

13 files changed

Lines changed: 114 additions & 24 deletions

File tree

MIGRATION.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,11 +266,11 @@ Affected SDKs: `@sentry/browser` and `@sentry/deno` (and their dependents).
266266

267267
The `console` option of `breadcrumbsIntegration` was removed. Use the `consoleIntegration` from `@sentry/core` to capture console breadcrumbs instead.
268268

269-
### Next.js: tracing removed from generated templates
269+
### `@sentry/nextjs`
270270

271-
Affected SDKs: `@sentry/nextjs`.
271+
**Tracing removed from generated templates:** Tracing was removed from the generated Pages Router API handler, Edge API handler, and Middleware wrapper templates. Route handlers and middleware are still instrumented automatically, so no action is required for most users.
272272

273-
Tracing was removed from the generated Pages Router API handler, Edge API handler, and Middleware wrapper templates. Route handlers and middleware are still instrumented automatically, so no action is required for most users.
273+
**Middleware span op changed to `middleware`:** Next.js middleware spans now use the `middleware` span op instead of `http.server.middleware`. If you filter or alert on the previous op (e.g. in dashboards or dynamic sampling rules), update it to `middleware`.
274274

275275
### Cloudflare: `nodejs_compat` compatibility flag is now required
276276

dev-packages/e2e-tests/test-applications/nextjs-15/tests/middleware.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ test('tracesSampler receives normalizedRequest for edge middleware', async ({ re
1414
const middlewareTransaction = await middlewareTransactionPromise;
1515

1616
expect(middlewareTransaction.contexts?.runtime?.name).toBe('vercel-edge');
17-
expect(middlewareTransaction.contexts?.trace?.op).toBe('http.server.middleware');
17+
expect(middlewareTransaction.contexts?.trace?.op).toBe('middleware');
1818
expect(middlewareTransaction.request?.url).toContain('/api/endpoint-behind-middleware');
1919
expect(middlewareTransaction.request?.method).toBe('GET');
2020
});

dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/middleware.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ test('Should create a transaction for middleware', async ({ request }) => {
1212
const middlewareTransaction = await middlewareTransactionPromise;
1313

1414
expect(middlewareTransaction.contexts?.trace?.status).toBe('ok');
15-
expect(middlewareTransaction.contexts?.trace?.op).toBe('http.server.middleware');
15+
expect(middlewareTransaction.contexts?.trace?.op).toBe('middleware');
1616
expect(middlewareTransaction.contexts?.runtime?.name).toBe('node');
1717
expect(middlewareTransaction.transaction_info?.source).toBe('route');
1818

dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/middleware.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ test.skip('Should create a transaction for middleware', async ({ request }) => {
2929
const middlewareTransaction = await middlewareTransactionPromise;
3030

3131
expect(middlewareTransaction.contexts?.trace?.status).toBe('ok');
32-
expect(middlewareTransaction.contexts?.trace?.op).toBe('http.server.middleware');
32+
expect(middlewareTransaction.contexts?.trace?.op).toBe('middleware');
3333
expect(middlewareTransaction.contexts?.runtime?.name).toBe('vercel-edge');
3434
expect(middlewareTransaction.transaction_info?.source).toBe('route');
3535

@@ -57,7 +57,7 @@ test.skip('Faulty middlewares', async ({ request }) => {
5757
await test.step('should record transactions', async () => {
5858
const middlewareTransaction = await middlewareTransactionPromise;
5959
expect(middlewareTransaction.contexts?.trace?.status).toBe('internal_error');
60-
expect(middlewareTransaction.contexts?.trace?.op).toBe('http.server.middleware');
60+
expect(middlewareTransaction.contexts?.trace?.op).toBe('middleware');
6161
expect(middlewareTransaction.contexts?.runtime?.name).toBe('vercel-edge');
6262
expect(middlewareTransaction.transaction_info?.source).toBe('route');
6363
});

dev-packages/e2e-tests/test-applications/nextjs-16-tunnel/tests/tunnel-route.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ test('Tunnel requests should not create middleware or fetch spans', async ({ pag
110110
expect(pageloadTransaction).toBeDefined();
111111
expect(pageloadTransaction.contexts?.trace?.op).toBe('pageload');
112112

113-
const middlewareTransactions = allTransactions.filter(tx => tx.contexts?.trace?.op === 'http.server.middleware');
113+
const middlewareTransactions = allTransactions.filter(tx => tx.contexts?.trace?.op === 'middleware');
114114

115115
// We WILL have a middleware transaction for GET / (the pageload)
116116
// But we should NOT have middleware transactions for POST requests (tunnel route)

dev-packages/e2e-tests/test-applications/nextjs-16/tests/middleware.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@ test('Should create a transaction for middleware', async ({ request }) => {
1717
const middlewareTransaction = await middlewareTransactionPromise;
1818

1919
expect(middlewareTransaction.contexts?.trace?.status).toBe('ok');
20-
expect(middlewareTransaction.contexts?.trace?.op).toBe('http.server.middleware');
20+
expect(middlewareTransaction.contexts?.trace?.op).toBe('middleware');
2121
expect(middlewareTransaction.contexts?.runtime?.name).toBe('node');
2222
expect(middlewareTransaction.transaction_info?.source).toBe('route');
2323

2424
expect(middlewareTransaction.request?.method).toBe('GET');
2525
expect(middlewareTransaction.request?.url).toContain('/api/endpoint-behind-middleware');
2626

27+
// The `Middleware.execute` OTEL root span is the only `middleware` span. The build-time
28+
// `wrapMiddlewareWithSentry` wrapper used to start a second, redundant one nested inside it.
29+
const nestedMiddlewareSpans = middlewareTransaction.spans?.filter(span => span.op === 'middleware');
30+
expect(nestedMiddlewareSpans).toHaveLength(0);
31+
2732
// Assert that isolation scope works properly
2833
expect(middlewareTransaction.tags?.['my-isolated-tag']).toBe(true);
2934
expect(middlewareTransaction.tags?.['my-global-scope-isolated-tag']).not.toBeDefined();
@@ -52,7 +57,7 @@ test('Faulty middlewares', async ({ request }) => {
5257
await test.step('should record transactions', async () => {
5358
const middlewareTransaction = await middlewareTransactionPromise;
5459
expect(middlewareTransaction.contexts?.trace?.status).toBe('internal_error');
55-
expect(middlewareTransaction.contexts?.trace?.op).toBe('http.server.middleware');
60+
expect(middlewareTransaction.contexts?.trace?.op).toBe('middleware');
5661
expect(middlewareTransaction.contexts?.runtime?.name).toBe('node');
5762
expect(middlewareTransaction.transaction_info?.source).toBe('route');
5863
});

dev-packages/e2e-tests/test-applications/nextjs-pages-dir/tests/middleware.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ test('Should create a transaction for middleware', async ({ request }) => {
1212
const middlewareTransaction = await middlewareTransactionPromise;
1313

1414
expect(middlewareTransaction.contexts?.trace?.status).toBe('ok');
15-
expect(middlewareTransaction.contexts?.trace?.op).toBe('http.server.middleware');
15+
expect(middlewareTransaction.contexts?.trace?.op).toBe('middleware');
1616
expect(middlewareTransaction.contexts?.runtime?.name).toBe('vercel-edge');
1717
expect(middlewareTransaction.transaction_info?.source).toBe('url');
1818

@@ -37,7 +37,7 @@ test('Faulty middlewares', async ({ request }) => {
3737
await test.step('should record transactions', async () => {
3838
const middlewareTransaction = await middlewareTransactionPromise;
3939
expect(middlewareTransaction.contexts?.trace?.status).toBe('internal_error');
40-
expect(middlewareTransaction.contexts?.trace?.op).toBe('http.server.middleware');
40+
expect(middlewareTransaction.contexts?.trace?.op).toBe('middleware');
4141
expect(middlewareTransaction.contexts?.runtime?.name).toBe('vercel-edge');
4242
expect(middlewareTransaction.transaction_info?.source).toBe('url');
4343
});

packages/nextjs/src/common/enhanceMiddlewareRootSpan.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op';
12
import { stripUrlQueryAndFragment } from '@sentry/core';
23
import { ATTR_NEXT_SPAN_NAME, ATTR_NEXT_SPAN_TYPE } from './nextSpanAttributes';
34

@@ -26,7 +27,7 @@ export function enhanceMiddlewareRootSpan(span: MutableMiddlewareRootSpan): void
2627
return;
2728
}
2829

29-
span.setOp('http.server.middleware');
30+
span.setOp(WEB_SERVER_MIDDLEWARE_SPAN_OP);
3031

3132
const spanName = attributes[ATTR_NEXT_SPAN_NAME];
3233
if (typeof spanName !== 'string' || !spanName || !span.getName()) {

packages/nextjs/src/edge/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { setUrlProcessingMetadata } from '../common/utils/setUrlProcessingMetada
2828
import { distDirRewriteFramesIntegration } from './distDirRewriteFramesIntegration';
2929
import { enhanceMiddlewareRootSpan } from '../common/enhanceMiddlewareRootSpan';
3030
import { SENTRY_KIND } from '@sentry/conventions/attributes';
31+
import { WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op';
3132

3233
export * from '@sentry/vercel-edge';
3334
export * from '../common';
@@ -134,7 +135,7 @@ export function init(options: VercelEdgeOptions = {}): void {
134135

135136
// Make sure middleware spans get the right op
136137
if (spanAttributes?.['next.span_type'] === 'Middleware.execute') {
137-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'http.server.middleware');
138+
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, WEB_SERVER_MIDDLEWARE_SPAN_OP);
138139
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'url');
139140
}
140141

packages/nextjs/src/server/enhanceHandleRequestRootSpan.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { HTTP_METHOD, HTTP_REQUEST_METHOD, HTTP_ROUTE, HTTP_TARGET } from '@sentry/conventions/attributes';
2+
import { WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op';
23
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, stripUrlQueryAndFragment } from '@sentry/core';
34
import { ATTR_NEXT_ROUTE, ATTR_NEXT_SPAN_NAME, ATTR_NEXT_SPAN_TYPE } from '../common/nextSpanAttributes';
45
import { TRANSACTION_ATTR_SENTRY_ROUTE_BACKFILL } from '../common/span-attributes-with-logic-attached';
@@ -64,7 +65,7 @@ export function enhanceHandleRequestRootSpan(span: MutableRootSpan): void {
6465

6566
if (middlewareMatch) {
6667
span.setName(`middleware ${middlewareMatch[1]}`);
67-
span.setOp('http.server.middleware');
68+
span.setOp(WEB_SERVER_MIDDLEWARE_SPAN_OP);
6869
}
6970

7071
// Next.js overrides transaction names for page loads that throw an error

0 commit comments

Comments
 (0)