nextjs - 💡(How to fix) Fix Next.js middleware tracing produces two traces per request [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#91281Fetched 2026-04-08 00:41:58
View on GitHub
Comments
1
Participants
2
Timeline
7
Reactions
0
Timeline (top)
labeled ×3closed ×1commented ×1issue_type_added ×1

Root Cause

I expect that for one server request, there is one associated opentelemetry trace. This is however not the case, because middleware tracing emits its own, entirely separate trace that is not at all connected to the base one.

Code Example

docker run --rm -it \
  -p 16686:16686 \
  -p 4317:4317 \
  jaegertracing/jaeger:latest

---

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.3.0: Wed Jan 28 20:48:41 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T6041
  Available memory (MB): 49152
  Available CPU cores: 14
Binaries:
  Node: 24.13.0
  npm: 11.6.2
  Yarn: 1.22.22
  pnpm: 10.32.1
Relevant Packages:
  next: 16.1.6 // Latest available version is detected (16.1.6).
  eslint-config-next: N/A
  react: 19.2.3
  react-dom: 19.2.3
  typescript: 5.9.3
Next.js Config:
  output: N/A
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/blairmcalpine/double-trace-repro

To Reproduce

  1. npm i
  2. npm run dev (don't visit the site on your browser, it just makes a bunch of noisy traces)
  3. (Requires docker, feel free to use any other local opentelemetry collector you wish)
docker run --rm -it \
  -p 16686:16686 \
  -p 4317:4317 \
  jaegertracing/jaeger:latest
  1. curl http://localhost:3000
  2. Visit localhost:16686. Filter by the double-trace-repro service, and observe that there is two entirely separate traces for the single request you sent to the server.

Current vs. Expected behavior

I expect that for one server request, there is one associated opentelemetry trace. This is however not the case, because middleware tracing emits its own, entirely separate trace that is not at all connected to the base one.

I can see this making sense previously when middleware ran in edge runtimes (so the work was done by an entirely separate machine), but now that we have proxy and the node runtime, it's always going to be handled by the same process. This currently happens because of how we invoke middleware, where we run an entirely new BaseServer.handleRequest span that gets its propagation info from the incoming requests' headers (ie not the propagation info from the already started parent span). Instead, tracing middleware should be tied to the parent GET /[template]/some-route span (that is also BaseServer.handleRequest).

This current behaviour is especially problematic if you want to use datadog for Next.js apps - this is because of datadog's trace metrics, which are computed from every single common trace emitted by a service. Because these two traces are both BaseServer.handleRequest, this means datadog things they are two separate requests. This means 2x the perceived traffic for a service, as well as incorrect averages for latency, etc.

To work around this I've had to create a "filter" span exporter that basically just drops this extra BaseServer.handleRequest span from middleware, but I'd love for it to just be included on the main trace instead like it should be.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.3.0: Wed Jan 28 20:48:41 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T6041
  Available memory (MB): 49152
  Available CPU cores: 14
Binaries:
  Node: 24.13.0
  npm: 11.6.2
  Yarn: 1.22.22
  pnpm: 10.32.1
Relevant Packages:
  next: 16.1.6 // Latest available version is detected (16.1.6).
  eslint-config-next: N/A
  react: 19.2.3
  react-dom: 19.2.3
  typescript: 5.9.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Middleware, Instrumentation

Which stage(s) are affected? (Select all that apply)

next dev (local), next start (local), Other (Deployed)

Additional context

No response

extent analysis

Fix Plan

To fix the issue of duplicate traces in OpenTelemetry, we need to modify the middleware tracing to be tied to the parent span instead of creating a new one.

Here are the steps:

  • Update the middleware invocation to use the existing parent span context.
  • Modify the BaseServer.handleRequest span to include the middleware tracing.

Example code:

// Get the current span
const currentSpan = tracing.getSpan();

// Create a new span for the middleware, tied to the parent span
const middlewareSpan = tracing.trace('middleware', {
  childOf: currentSpan,
});

// Run the middleware with the new span
middlewareSpan.run(() => {
  // Middleware code here
});

Alternatively, you can use the tracer.startSpan method with the childOf option to create a new span that is a child of the current span:

const tracer = tracing.getTracer();
const middlewareSpan = tracer.startSpan('middleware', {
  childOf: currentSpan,
});

Verification

To verify that the fix worked, you can check the OpenTelemetry traces in Jaeger or Datadog to ensure that there is only one trace per request, and that the middleware tracing is included in the main trace.

You can also add logging or debugging statements to verify that the middleware span is being created correctly and that it is tied to the parent span.

Extra Tips

  • Make sure to update the tracer and tracing variables to match your actual OpenTelemetry setup.
  • If you are using a different tracing library or framework, you may need to modify the code accordingly.
  • You can also use the tracer.getCurrentSpan method to get the current span, instead of using the tracing.getSpan method.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING