Skip to content

~50% of Session Replays getting discarded client-side (mostly Internal SDK Error, some Network Error) in Next.js app using '/tunnel' reverse proxy #22169

Description

@cstavitsky

Opening on behalf of a customer:

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/nextjs

SDK Version

sentry.javascript.nextjs 10.62.0

Framework Version

No response

Link to Sentry event

N/A - Replays are getting discarded client-side

Reproduction Example/SDK Setup

// This file configures the initialization of Sentry on the client.
// The added config here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/

import * as Sentry from '@sentry/nextjs'
import { isSentryActive } from '@/app/hooks/sessionReplayProvider'

// Must match the identifier produced by scripts/upload_sourcemap_to_sentry.mjs
// — `ourcompany-ui-<env>@<5-char-git-hash>` — so reported errors map back to the
// uploaded source maps. If either side drifts, stack traces stay unsymbolicated.
// hosts so the trial flow can be exercised end-to-end during development.
// Remove this OR flag before merge — non-prod traffic should not hit Sentry.
const isProductionHost =
  process.env.NEXT_PUBLIC_SELF_BASE_URL === 'https://app.ourcompany.co/'
const releaseVersion = process.env.NEXT_PUBLIC_RELEASE_VERSION
const sentryRelease = releaseVersion
  ? `ourcompany-ui-${isProductionHost ? 'prod' : 'internal'}@${releaseVersion}`
  : undefined

// Two gates, both must pass:
//   1. isProductionHost — same host gate sentry uses, keeps non-prod
//      traffic out of the Sentry project.
//   2. isSentryActive() — honors the SESSION_REPLAY_PROVIDER toggle in
//      src/app/hooks/sessionReplayProvider.ts so the chosen provider is the
//      *only* one initialized.
if (isProductionHost && isSentryActive()) {
  // Skip the replay integration entirely for Impersonation sessions. Replay
  // auto-starts at SDK init (before any React hook runs), so a runtime guard
  // in useSentry can't prevent the recording — only omitting the integration
  // here does. Mirrors the cookie check in src/app/hooks/useSentry.tsx.
  const isImpersonationSession =
    typeof document !== 'undefined' &&
    document.cookie.split('; ').some((c) => c.startsWith('_impersonation='))

  const replayIntegration = Sentry.replayIntegration({
    maskAllText: false,
    maskAllInputs: false,
    mask: ['.sentry-mask', '[data-pii]'],
    block: ['.sentry-block'],
    blockAllMedia: false,
    minReplayDuration: 5000,
    networkDetailAllowUrls: [window.location.origin],
    attachRawBodyFromRequest: true,
    networkRequestHeaders: [
      'Content-Type',
      'Authorization',
      'Accept',
      'X-Request-ID',
      'traceparent',
    ],
    networkResponseHeaders: [
      'Content-Type',
      'X-Request-ID',
      'X-RateLimit-Remaining',
      'Cache-Control',
      'traceparent',
    ],
    networkCaptureBodies: true,
  })

  Sentry.init({
    dsn: DSN,
    tunnel: 'https://tunnel.ourcompany.co/',

    release: sentryRelease,

    integrations: [
      Sentry.consoleLoggingIntegration({ levels: ['error', 'warn'] }),
      Sentry.captureConsoleIntegration(),
      ...(isImpersonationSession ? [] : [replayIntegration]),
    ],

    // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
    tracesSampleRate: 1,
    // Enable logs to be sent to Sentry
    enableLogs: true,

    // Define how likely Replay events are sampled.
    // This sets the sample rate to be 10%. You may want this to be 100% while
    // in development and sample at a lower rate in production
    replaysSessionSampleRate: 1.0,

    // Define how likely Replay events are sampled when an error occurs.
    replaysOnErrorSampleRate: 1.0,

    // Enable sending user PII (Personally Identifiable Information)
    // https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii
    sendDefaultPii: true,
  })
}

// Safe to export even when Sentry.init was skipped — the SDK function is a
// no-op without an active client, so Next.js router transitions just do nothing.
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart

Steps to Reproduce

Hard to provide exact steps to reproduce, opened ticket to try to isolate the problem.

Context:

Please see linked shadow ticket for customer-specific context and extra logs, nginx config

  • Customer wants to capture 100% of all replay sessions, which is why the 100% sample rates. Not sure whether having a 100% sample rate on general sessions is impacting this.
  • Per the replay config, bodies & raw bodies & headers are being captured in these replays. Not sure if that is making them heavierweight and more likely to experience issues and timeout or be dropped.
  • Data is currently sent to nginx reverse proxy via Next.js tunnel option. (NOTE: customer does not want to use tunnelRoute - tunnelRoute "would send traffic to our own next js server which we don't want as it would overload our production servers and can cause downtime for us.")
  • Stats for Session Replays in a recent 24-hour window:
    Total 683,220
    Accepted 343,771
    Filtered (Server) 38,536
    Discarded (Client) 300,887 (~80% Internal SDK Error, ~20% Network Error)
    Dropped (Server) 26

Expected Result

The vast majority of Session Replays are not dropped.

Actual Result

~50% of Replays are discarded client-side due to Internal SDK Error (~80%) and Network Error (~20%)

Troubleshooting Steps Taken

  • client_max_body_size was raised to 30M on nginx

  • Customer temporarily ran debug: true in production. Due to the captureConsole integration also being on, this output was captured as Sentry issues. Not sure if these are directly the cause or not of Internal SDK Errors and would like another opinion.

Captured issues during 24-hour specific period:

Image

Admin of replays during identical 24-hour period:

Image

Regarding one of these, Sentry Logger [warn]: [Replay]  Parsing text body from response timed out - Possibly relevant: https://github.com/getsentry/sentry-javascript/blob/develop/packages/replay-internal/src/coreHandlers/util/fetchUtils.ts#L278-L294

Not sure if these are actually the source of the issue or not but seems promising and would like guidance.

  • Customer also shared a separate snippet of debug logs that looked completely normal for replay (linked in shadow ticket) - not sure if this is really representative of problem or can be ignored

  • Asked customer to enable internal experimental flag to capture exceptions, but it didn't pick up anything meaningful. Seems like that flag may not be working in general.

  • Asked the customer to check their nginx reverse proxy's 400 errors, thinking the reason for dropped events may be on the proxy side. They shared a screenshot where nginx showed seemingly relatively low number of 4XX in a 24 hour window compared to the stats of dropped events:

Image
  • Customer nginx sample logs and config - will be shared in linked shadow ticket

Additional Context

No response

Priority

React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it.

Metadata

Metadata

Assignees

No one assigned

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Waiting for: Product Owner

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions