Link to the code that reproduces this issue
https://github.com/pnodet/next-prerender-random-repro
To Reproduce
pnpm install && pnpm dev
curl http://localhost:3000/early-span → render fails with next-prerender-random
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)
Link to the code that reproduces this issue
https://github.com/pnodet/next-prerender-random-repro
To Reproduce
pnpm install && pnpm devcurl http://localhost:3000/early-span→ render fails withnext-prerender-randomcurl http://localhost:3000/late-span→ identicalstartSpan, worksThe two pages differ only in when
trace.getTracer()was called.instrumentation.tsacquires one tracer before registering a plainNodeTracerProvider(no Sentry or any vendor SDK involved), which is the standard constructor pattern of OTel instrumentation libraries (InstrumentationAbstractcallsthis._tracer = trace.getTracer(...)in its constructor).Current vs. Expected behavior
Current: #82350 ("Allow span creation while prerendering") patches
trace.getTracerProvider().getTracerso span creation exitsworkUnitAsyncStorage. But a tracer acquired before the delegate is registered on the globalProxyTracerProvideris aProxyTracer, andProxyTracer._getTracer()resolves the real tracer throughProxyTracerProvider.getDelegateTracer()— which the patch does not wrap. Span-id generation then runsRandomIdGenerator's rawMath.random()inside the prerender scope:Expected: spans created from instrumentation-acquired tracers are allowed during prerendering, per #82350's stated intent.
Real-world impact
This is how
@sentry/nextjsbreakscacheComponentsapps in production:@sentry/node's_initconstructs all integrations (pg, undici, http — each acquiring aProxyTracer) beforeinitOpenTelemetry()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-lessError: null(StaticGenBailoutErrorhas no message; the real error is only in server logs). Production stack on next@16.2.9 + @sentry/nextjs@10.57.0 — note theProxyTracerframe: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 wrapProxyTracerProvider.getDelegateTracer(or wrap the delegate provider'sgetTracerwhen the delegate is set), so tracers resolved lazily by pre-registrationProxyTracers get the sameworkUnitAsyncStorage.exittreatment.Provide environment information
Which area(s) are affected?
Cache Components, Instrumentation
Which stage(s) are affected?
next dev (local), next start (local), Vercel (Deployed)