How to Fix: “use cache” with vercel otel – used `Math.random()` outside of `”use cache”`
That Math.random() error is not really about your code being random—it is about cache scope pollution caused by OpenTelemetry wrapping execution inside a “use cache” path.
When a Next.js app uses “use cache” and Vercel OpenTelemetry together, development mode can surface an error like used Math.random() outside of "use cache" even when your component never directly relies on randomness for rendering. The failure happens because Next.js treats functions executed during a cached render as needing to be deterministic, while some tracing code or wrapped runtime behavior introduces access to non-deterministic APIs such as Math.random().
Understanding the Root Cause
This bug appears at the intersection of three systems:
- Next.js cache components, especially code guarded by
"use cache". - Deterministic rendering rules enforced by React and Next.js.
- OpenTelemetry instrumentation provided through Vercel or custom tracing setup.
Code inside a "use cache" boundary must be reproducible. That means it cannot depend on values that change on every call, such as:
Math.random()Date.now()- request-specific mutable state
- non-stable tracing metadata generated during render
In the reproduction linked in the issue, the app works until tracing is enabled in development. At that point, OpenTelemetry hooks can wrap server execution, create spans, attach identifiers, or invoke helper code that internally uses random value generation. Even if that randomness does not come from your page component directly, Next.js still sees it as happening during a cached render execution path.
That is why the message can feel misleading: your app may not explicitly call Math.random(), but a dependency in the same render context does.
In practice, the issue is usually triggered because:
- a page, layout, or server function uses
"use cache" - that same call stack gets wrapped by OpenTelemetry instrumentation
- the instrumentation or a related library uses non-deterministic APIs
- Next.js rejects the render because cached output must stay stable
Development mode can make this easier to reproduce because tracing, validation, and runtime checks are more aggressive there.
Step-by-Step Solution
The safest fix is to keep tracing and cached rendering separated. Do not let instrumentation execute inside code paths that Next.js expects to be deterministic.
1. Identify where "use cache" is applied
Search your app for cached server functions, pages, or helpers.
grep -R 'use cache' app src
You are looking for code like this:
export async function getCachedData() {
"use cache"
return fetch("https://example.com/api").then((r) => r.json())
}
If this function is invoked while OpenTelemetry creates spans around it, the cached execution path may become non-deterministic.
2. Move tracing outside the cached function
If you manually create spans, wrap the caller, not the cached function itself.
Problematic pattern:
import { trace } from "@opentelemetry/api"
export async function getCachedData() {
"use cache"
const tracer = trace.getTracer("app")
return tracer.startActiveSpan("getCachedData", async (span) => {
try {
const res = await fetch("https://example.com/api")
return res.json()
} finally {
span.end()
}
})
}
Safer pattern:
import { trace } from "@opentelemetry/api"
async function getCachedData() {
"use cache"
const res = await fetch("https://example.com/api")
return res.json()
}
export async function loadHomePageData() {
const tracer = trace.getTracer("app")
return tracer.startActiveSpan("loadHomePageData", async (span) => {
try {
return await getCachedData()
} finally {
span.end()
}
})
}
This keeps the actual cached function deterministic while still letting you trace the higher-level operation.
3. Avoid non-deterministic helpers inside cached code
Check for any direct or indirect use of:
Math.random()crypto.randomUUID()Date.now()- generated IDs for logs or spans
Bad inside cached code:
export async function getProduct() {
"use cache"
const requestId = Math.random().toString(36)
console.log(requestId)
return { ok: true }
}
Better:
export async function getProduct() {
"use cache"
return { ok: true }
}
export async function handleProductRequest() {
const requestId = Math.random().toString(36)
console.log(requestId)
return getProduct()
}
4. Keep instrumentation setup in the recommended files only
If you are using Next.js instrumentation support, keep OpenTelemetry bootstrap code in the framework-approved files such as instrumentation.ts and avoid importing tracing setup into cached render helpers.
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("./tracing/node")
}
}
Then place your tracing initialization in a separate module:
import "@vercel/otel"
export function initTracing() {
// tracing bootstrap only
}
The key idea is simple: initialize tracing at app startup, not during a cached render function.
5. If needed, remove "use cache" from the affected path
If a function must always participate in dynamic tracing or request-scoped work, it may not be a good candidate for cache semantics.
export async function getDynamicData() {
const res = await fetch("https://example.com/api", {
cache: "no-store"
})
return res.json()
}
This trades cacheability for correctness. Use it only when you truly need dynamic behavior.
6. Upgrade Next.js and related telemetry packages
This type of bug often sits on a framework boundary. Before applying workarounds everywhere, update:
- next
- react and react-dom
- @vercel/otel
- any OpenTelemetry SDK packages
Then retest in development and production separately. Some regressions are version-specific.
npm install next@latest react@latest react-dom@latest @vercel/otel@latest
7. Validate the fix
After refactoring, run the app again:
npm run dev
Visit the affected page and confirm:
- the
Math.random()/"use cache"error is gone - traces still appear as expected
- cached data remains stable across renders
Common Edge Cases
Indirect randomness from libraries
You may remove all visible Math.random() calls and still hit the error. In that case, a logging, tracing, or ID-generation package is likely doing it for you. Audit imports used inside cached functions.
Request-scoped context in cached helpers
Using headers, cookies, auth context, or span context from inside a cached function can also violate determinism. Even without random numbers, request-bound values can make cache output invalid.
Development-only failures
If the issue only appears in next dev, do not ignore it. Development mode may simply be exposing a real production correctness problem earlier. Still, test both environments because some tracing hooks differ by runtime.
Tracing wrappers around fetch
If your telemetry integration patches fetch, the error can occur even when your cached function only performs a normal network request. That is another sign that the wrapper belongs outside the cached boundary.
Server Components mixed with dynamic utilities
A Server Component that looks static can become dynamic when it imports a helper that reads time, generates IDs, or starts spans. Keep utility modules used by cached components extremely clean.
FAQ
Why does Next.js complain about Math.random() when I never called it?
Because the call often comes from a dependency such as OpenTelemetry, a logger, or an ID generator executing in the same cached render stack. Next.js flags the whole execution path, not only your top-level source line.
Can I still use OpenTelemetry with "use cache"?
Yes, but you should instrument outside the cached function. Trace the request handler, action, or orchestration layer, and keep the cached function itself deterministic.
Should I remove "use cache" or disable tracing?
Usually neither globally. First, isolate tracing from cached code. Only remove "use cache" for paths that genuinely require dynamic or request-scoped behavior. Disabling tracing should be a last resort.
The durable fix is to respect the boundary: cached render logic must stay deterministic, and observability logic must not leak non-deterministic behavior into that boundary. Once you separate those concerns, the Vercel OpenTelemetry integration and Next.js caching model can work together reliably.