Skip to content

cacheComponents: OTel span creation bypasses the #82350 tracer patch via ProxyTracer.getDelegateTracer, failing prerenders with next-prerender-random #94753

@pnodet

Description

@pnodet

Link to the code that reproduces this issue

https://github.com/pnodet/next-prerender-random-repro

To Reproduce

  1. pnpm install && pnpm dev
  2. curl http://localhost:3000/early-span → render fails with next-prerender-random
  3. curl http://localhost:3000/late-span → identical startSpan, works

The two pages differ only in when trace.getTracer() was called. instrumentation.ts acquires one tracer before registering a plain NodeTracerProvider (no Sentry or any vendor SDK involved), which is the standard constructor pattern of OTel instrumentation libraries (InstrumentationAbstract calls this._tracer = trace.getTracer(...) in its constructor).

Current vs. Expected behavior

Current: #82350 ("Allow span creation while prerendering") patches trace.getTracerProvider().getTracer so span creation exits workUnitAsyncStorage. But a tracer acquired before the delegate is registered on the global ProxyTracerProvider is a ProxyTracer, and ProxyTracer._getTracer() resolves the real tracer through ProxyTracerProvider.getDelegateTracer() — which the patch does not wrap. Span-id generation then runs RandomIdGenerator's raw Math.random() inside the prerender scope:

Error: Route "/early-span" used `Math.random()` before accessing either uncached data (e.g. `fetch()`) or Request data (...). See more info here: https://nextjs.org/docs/messages/next-prerender-random

Expected: spans created from instrumentation-acquired tracers are allowed during prerendering, per #82350's stated intent.

Real-world impact

This is how @sentry/nextjs breaks cacheComponents apps in production: @sentry/node's _init constructs all integrations (pg, undici, http — each acquiring a ProxyTracer) before initOpenTelemetry() registers the provider. Any instrumented operation in a prerenderable scope (e.g. a Postgres query in a PPR shell) then fails every ISR revalidation of the route — the static shell never refreshes, on-demand renders can 500, and Sentry reports it as a message-less Error: null (StaticGenBailoutError has no message; the real error is only in server logs). Production stack on next@16.2.9 + @sentry/nextjs@10.57.0 — note the ProxyTracer frame:

Error: Route "/product/[category]" used `Math.random()` before accessing either uncached data ...
    at RandomIdGenerator.generateId [as generateSpanId] (@opentelemetry/sdk-trace-base/src/platform/node/RandomIdGenerator.ts:31:41)
    at Tracer.startSpan (@opentelemetry/sdk-trace-base/src/Tracer.ts:82:38)
    at ProxyTracer.startSpan (@opentelemetry/api/src/trace/ProxyTracer.ts:41:30)
    at BoundPool.connect (@sentry/node/src/integrations/tracing/postgres/vendored/instrumentation.ts:549:36)

Likely the unresolved cause behind #72572 and the remaining reports on #72024.

Suggested fix

In extendTracerProviderForCacheComponents (packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts), also wrap ProxyTracerProvider.getDelegateTracer (or wrap the delegate provider's getTracer when the delegate is set), so tracers resolved lazily by pre-registration ProxyTracers get the same workUnitAsyncStorage.exit treatment.

Provide environment information

Operating System:
  Platform: darwin / Linux (Vercel production)
  Arch: arm64
Binaries:
  Node: 24.14.1
  pnpm: 10.33.4
Relevant Packages:
  next: 16.2.9
  react: 19.2.7
  react-dom: 19.2.7
  typescript: 5.9.3
  @opentelemetry/api: 1.9.1
  @opentelemetry/sdk-trace-node: 2.7.1
Next.js Config:
  cacheComponents: true

Which area(s) are affected?

Cache Components, Instrumentation

Which stage(s) are affected?

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions