Skip to content

Conversation

@kakadiadarpan
Copy link

What?

This PR fixes OpenTelemetry trace context propagation when requests pass through Next.js middleware. Previously, middleware traces (edge runtime) and server traces (Node.js runtime) were created as separate, independent traces. Now they are properly connected as parent-child spans within a single unified trace.

Changes:

  • Modified packages/next/src/server/web/adapter.ts to capture and propagate trace context from middleware spans to server requests
  • Updated test expectations in test/e2e/opentelemetry/instrumentation/opentelemetry.test.ts to verify the correct span hierarchy

Why?

When a request goes through middleware in Next.js, two separate runtime environments are involved:

  1. Edge runtime - Where middleware/proxy executes
  2. Node.js runtime - Where the actual route handlers execute

Before this fix, these two environments created disconnected traces:

Trace 1: [traceId: abc123]
└─ Middleware.execute [spanId: 111, parentId: root]

Trace 2: [traceId: abc123]  ❌ Disconnected!
└─ BaseServer.handleRequest [spanId: 222, parentId: root]

After the fix:

Trace: [traceId: abc123]
└─ Middleware.execute [spanId: 111, parentId: root]
   └─ BaseServer.handleRequest [spanId: 222, parentId: 111]  ✅ Connected!

The Problem:

  • Both spans share the same traceId (if an external traceparent header was provided)
  • But they have no parent-child relationship - both point to the same root parent
  • The W3C Trace Context headers (traceparent, tracestate) were not being propagated from middleware to the server

How?

The fix implements W3C Trace Context propagation across the edge → Node.js runtime boundary by leveraging Next.js's existing header propagation mechanism (x-middleware-request-* headers).

@ijjk
Copy link
Member

ijjk commented Nov 7, 2025

Allow CI Workflow Run

  • approve CI run for commit: a975b77

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

@kakadiadarpan kakadiadarpan changed the title fix: propagate opentelemetry context via middleware response headers fix: trace context propagation from middleware → server Nov 7, 2025
Comment on lines +508 to +511
finalResponse.headers.set(
'x-middleware-override-headers',
middlewareOverrideHeaders + ',' + overwrittenHeaders.join(',')
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
finalResponse.headers.set(
'x-middleware-override-headers',
middlewareOverrideHeaders + ',' + overwrittenHeaders.join(',')
)
const headerValue = middlewareOverrideHeaders
? middlewareOverrideHeaders + ',' + overwrittenHeaders.join(',')
: overwrittenHeaders.join(',')
finalResponse.headers.set('x-middleware-override-headers', headerValue)

When setting the x-middleware-override-headers header at line 510, the code concatenates middlewareOverrideHeaders (which can be null) with a string, resulting in the literal string "null" being prepended to the header value. This corrupts the header when trace context is captured but no previous override headers exist.

View Details

Analysis

Null concatenation in x-middleware-override-headers header when trace context is captured without existing overrides

What fails: In packages/next/src/server/web/adapter.ts lines 507-512, when middleware captures trace context but no previous override headers exist, the code concatenates null with string values, resulting in the literal string "null" being prepended to the header value.

How to reproduce:

  1. Middleware runs and captures trace context (triggers middlewareTraceContext.length > 0)
  2. No previous middleware override headers exist (middlewareOverrideHeaders is null from finalResponse.headers.get())
  3. Line 510 executes: middlewareOverrideHeaders + ',' + overwrittenHeaders.join(',')
  4. Result: null + ',' + 'traceparent,tracestate' = "null,traceparent,tracestate"

Result: The x-middleware-override-headers header contains the literal string "null" as the first element, which corrupts header propagation and may break trace propagation in the server-side code.

Expected: When middlewareOverrideHeaders is null, the header should be set to only the overwritten headers list without the "null" prefix, i.e., "traceparent,tracestate".

Root cause: The refactoring in commit a711b1c ("propagate context in middleware response headers") moved the header update outside the if (middlewareOverrideHeaders) conditional block, but did not add a null check when concatenating the values.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants