Skip to content

makeNodeTransport silently drops server-side events with Next.js 16 + Turbopack #18871

@owittek

Description

@owittek

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

10.32.1

Framework Version

Next.js 16.1.13

Link to Sentry event

No response

Reproduction Example/SDK Setup

Minimal reproduction:

# 1. Create fresh Next.js 16 project with default settings
pnpm dlx create-next-app@latest sentry-test

cd sentry-test

# 2. Run Sentry wizard with defaults
pnpm dlx @sentry/wizard@latest -i nextjs

# 3. Start dev server (uses Turbopack by default)
pnpm dev

# 4. Visit http://localhost:3000/sentry-example-page and click "Throw Sample Error"
# 5. Check Sentry dashboard - backend error never appears

The wizard generates these files with default settings:

package.json:

{
  "dependencies": {
    "@sentry/nextjs": "^10.34.0",
    "next": "16.1.3",
    "react": "19.2.3"
  }
}

sentry.server.config.ts (wizard-generated, unmodified):

import * as Sentry from "@sentry/nextjs";

Sentry.init({
  dsn: "https://[email protected]/xxx",
  tracesSampleRate: 1,
  enableLogs: true,
  sendDefaultPii: true,
});

instrumentation.ts (wizard-generated, unmodified):

import * as Sentry from "@sentry/nextjs";

export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    await import("./sentry.server.config");
  }
  if (process.env.NEXT_RUNTIME === "edge") {
    await import("./sentry.edge.config");
  }
}

export const onRequestError = Sentry.captureRequestError;

app/api/sentry-example-api/route.ts (wizard-generated test route):

import * as Sentry from "@sentry/nextjs";
export const dynamic = "force-dynamic";

class SentryExampleAPIError extends Error {
  constructor(message: string | undefined) {
    super(message);
    this.name = "SentryExampleAPIError";
  }
}

export function GET() {
  Sentry.logger.info("Sentry example API called");
  throw new SentryExampleAPIError(
    "This error is raised on the backend called by the example page.",
  );
}

Steps to Reproduce

  1. Create a fresh Next.js 16 project: pnpm dlx create-next-app@latest
  2. Run Sentry wizard: pnpm dlx @sentry/wizard@latest -i nextjs (accept all defaults)
  3. Start dev server: pnpm dev (uses Turbopack by default in Next.js 16)
  4. Visit http://localhost:3000/sentry-example-page
  5. Click "Throw Sample Error" button
  6. Observe: Frontend error appears in Sentry
  7. Backend error (SentryExampleAPIError) never appears

Expected Result

Both the frontend error (SentryExampleFrontendError) and backend error (SentryExampleAPIError) should appear in the Sentry dashboard.

Actual Result

  • Frontend error appears in Sentry
  • Backend/server-side error never appears
  • No errors in console
  • onRequestError hook is called (verified with logging)
  • Transport returns HTTP 200 from Sentry ingest API
  • Events are silently lost

Additional Context

Proof it's the SDK transport:

Using a custom fetch-based transport with identical envelope data works:

// sentry.server.config.ts
import * as Sentry from "@sentry/nextjs";

function makeFetchTransport(options: Parameters<typeof Sentry.makeNodeTransport>[0]) {
  return Sentry.createTransport(options, async (request) => {
    const response = await fetch(options.url, {
      method: "POST",
      body: request.body as BodyInit,
      headers: options.headers,
    });
    return {
      statusCode: response.status,
      headers: {
        "x-sentry-rate-limits": response.headers.get("X-Sentry-Rate-Limits"),
        "retry-after": response.headers.get("Retry-After"),
      },
    };
  });
}

Sentry.init({
  dsn: "...",
  transport: makeFetchTransport, // <-- Backend errors now appear in Sentry
});

Additional findings:

Test Result
Fresh create-next-app + Sentry wizard (default settings) ❌ Backend errors lost
Same setup with next dev --no-turbopack (Webpack) ✅ Works
Same http.request + stream.pipe code in standalone Node.js ✅ Works
Custom fetch transport (workaround above) ✅ Works
Client-side errors ✅ Works

Additional Context

Suspected root cause

The suppressTracing() wrapper in @sentry/node-core/transports/http.js manipulates OpenTelemetry async context. This may interact badly with Turbopack's async context handling:

// From @sentry/node-core/build/esm/transports/http.js
return new Promise((resolve, reject) => {
  suppressTracing(() => {  // <-- Async context manipulation
    let body = streamFromBody(request.body);
    // ...
    body.pipe(req);  // <-- Stream callbacks may not fire correctly in Turbopack
  });
});

This is similar to #18866 where suppressTracing() causes tracing spans to be suppressed in serverless environments.


I'm unfamiliar with the intricritacties of OpenTelemetry so using the suggested fix by Opus 4.5 of directly calling fetch() instead feels wrong to me but I'm providing it just in case.

Suggested fix

Since Node.js 18+ has native fetch(), use it by default:

if (typeof globalThis.fetch === 'function') {
  return createTransport(options, async (request) => {
    const response = await fetch(options.url, {
      method: 'POST',
      body: request.body,
      headers: options.headers,
    });
    return { statusCode: response.status, headers: {...} };
  });
}

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

    Projects

    Status

    Waiting for: Product Owner

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions