Environment
@sentry/nextjs 10.57.0 (also confirmed still present in @sentry-internal/browser-utils at the latest release, 10.74.0)
- Next.js 16.2.12, Turbopack build
- Public production page, real-user traffic (not reproduced synthetically)
What happened
A visitor's browser threw an uncaught error on a public page load:
Uncaught TypeError: Cannot read properties of undefined (reading 'startTime')
at et.reportAllChanges (<anonymous>:2:19429)
at <anonymous>:2:13070
at <anonymous>:2:331
at d (<anonymous>:2:6141)
at <anonymous>:2:6326
at <anonymous>:2:2895
at n.timeout (<anonymous>:2:5652)
tracesSampleRate: 0 was set, but the SDK still registers browserTracingIntegration, which auto-adds webVitalsIntegration, which observes LCP via the vendored web-vitals fork in @sentry-internal/browser-utils.
Root cause (verified against source, not just the bundle)
packages/browser-utils/src/metrics/web-vitals/getLCP.ts, onLCP's handleEntries:
https://github.com/getsentry/sentry-javascript/blob/10.57.0/packages/browser-utils/src/metrics/web-vitals/getLCP.ts#L52-L67
for (const entry of entries) {
lcpEntryManager._processEntry(entry);
if (entry.startTime < visibilityWatcher.firstHiddenTime) {
entry is read from the entries array with no check that it's actually defined. If any element of that array is falsy, this throws exactly the error above.
I checked this three ways before filing:
- The installed
@sentry-internal/browser-utils@10.57.0 bundle in node_modules — same unguarded code, matches the production stack trace.
- Sentry's own source at the
10.57.0 tag (linked above) — identical.
- Sentry's own source at the latest release (
10.74.0) — same unguarded loop, so this isn't already fixed:
https://github.com/getsentry/sentry-javascript/blob/10.74.0/packages/browser-utils/src/metrics/web-vitals/getLCP.ts#L60-L66
Compared to upstream web-vitals
packages/browser-utils/src/metrics/web-vitals/README.md documents this as a manually-vendored, frozen fork of GoogleChrome/web-vitals v5.1.0 (commit e22d23b), not auto-synced. Current upstream web-vitals (src/onLCP.ts) has since added a guard in the same loop:
for (const entry of entries) {
if (!entry) continue;
...
(Added in GoogleChrome/web-vitals#308, "Add Soft Navigation support" — so it's possible the original motivation was soft-nav-specific, and I can't say for certain that's the same mechanism producing an undefined entry in a non-soft-nav SDK build. But the defect itself — no defensive check on an array element before dereferencing it — is real and present in Sentry's fork regardless of why upstream added the check.)
What I could not confirm
I don't have a minimal reproduction for why entries contains a falsy element in this case — only the real-user stack trace and the confirmed absence of a guard that would prevent the crash if it does. Happy to share more from our Sentry project if that helps narrow it down.
Suggested fix
Add the same defensive check Sentry already has to pull in elsewhere in this vendored copy — if (!entry) continue; at the top of the for loop in handleEntries — cheap, matches upstream's current shape, and turns a hard crash into a silent skip for whatever produces the falsy element.
Workaround in place on our end
We filter browserTracingIntegration/webVitalsIntegration out of integrations entirely (since we don't use tracing — tracesSampleRate: 0), which sidesteps this rather than fixing it. Happy to share that filter if useful context, but wanted to report the underlying null-safety gap rather than just work around it silently.
Environment
@sentry/nextjs10.57.0 (also confirmed still present in@sentry-internal/browser-utilsat the latest release, 10.74.0)What happened
A visitor's browser threw an uncaught error on a public page load:
tracesSampleRate: 0was set, but the SDK still registersbrowserTracingIntegration, which auto-addswebVitalsIntegration, which observes LCP via the vendoredweb-vitalsfork in@sentry-internal/browser-utils.Root cause (verified against source, not just the bundle)
packages/browser-utils/src/metrics/web-vitals/getLCP.ts,onLCP'shandleEntries:https://github.com/getsentry/sentry-javascript/blob/10.57.0/packages/browser-utils/src/metrics/web-vitals/getLCP.ts#L52-L67
entryis read from theentriesarray with no check that it's actually defined. If any element of that array is falsy, this throws exactly the error above.I checked this three ways before filing:
@sentry-internal/browser-utils@10.57.0bundle innode_modules— same unguarded code, matches the production stack trace.10.57.0tag (linked above) — identical.10.74.0) — same unguarded loop, so this isn't already fixed:https://github.com/getsentry/sentry-javascript/blob/10.74.0/packages/browser-utils/src/metrics/web-vitals/getLCP.ts#L60-L66
Compared to upstream web-vitals
packages/browser-utils/src/metrics/web-vitals/README.mddocuments this as a manually-vendored, frozen fork ofGoogleChrome/web-vitalsv5.1.0 (commite22d23b), not auto-synced. Current upstreamweb-vitals(src/onLCP.ts) has since added a guard in the same loop:(Added in
GoogleChrome/web-vitals#308, "Add Soft Navigation support" — so it's possible the original motivation was soft-nav-specific, and I can't say for certain that's the same mechanism producing an undefined entry in a non-soft-nav SDK build. But the defect itself — no defensive check on an array element before dereferencing it — is real and present in Sentry's fork regardless of why upstream added the check.)What I could not confirm
I don't have a minimal reproduction for why
entriescontains a falsy element in this case — only the real-user stack trace and the confirmed absence of a guard that would prevent the crash if it does. Happy to share more from our Sentry project if that helps narrow it down.Suggested fix
Add the same defensive check Sentry already has to pull in elsewhere in this vendored copy —
if (!entry) continue;at the top of theforloop inhandleEntries— cheap, matches upstream's current shape, and turns a hard crash into a silent skip for whatever produces the falsy element.Workaround in place on our end
We filter
browserTracingIntegration/webVitalsIntegrationout ofintegrationsentirely (since we don't use tracing —tracesSampleRate: 0), which sidesteps this rather than fixing it. Happy to share that filter if useful context, but wanted to report the underlying null-safety gap rather than just work around it silently.