Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
re-export span api
  • Loading branch information
chargome committed Nov 24, 2025
commit 5cf5d09d72c47734b96a5c9cf8e3ac04089b8224
3 changes: 3 additions & 0 deletions packages/nextjs/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { applyTunnelRouteOption } from './tunnelRoute';
export * from '@sentry/react';
export * from '../common';
export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error';

// Override core span methods with Next.js-specific implementations that support Cache Components
export { startSpan, startSpanManual, startInactiveSpan } from '../common/utils/nextSpan';
export { browserTracingIntegration } from './browserTracingIntegration';
export { captureRouterTransitionStart } from './routing/appRouterRoutingInstrumentation';

Expand Down
90 changes: 90 additions & 0 deletions packages/nextjs/src/common/utils/nextSpan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { Span, StartSpanOptions } from '@sentry/core';
import {
SentryNonRecordingSpan,
startInactiveSpan as coreStartInactiveSpan,
startSpan as coreStartSpan,
startSpanManual as coreStartSpanManual,
} from '@sentry/core';

/**
* Check if we're currently in a Next.js Cache Components context.
* Cache Components are rendered during the production build phase.
*
* @returns true if we're in a Cache Components context, false otherwise
* // todo: This is a heuristic check, we should use a more reliable way to detect a Cache Components context once Vercel exposes it.
*/
function isCacheComponentContext(): boolean {
return process.env.NEXT_PHASE === 'phase-production-build';
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So right now now, all startSpan operations performed by users will be using the non-recording spans?

I suppose that means they can't manually instrument anything until the heuristic gets changed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be phase-production-build only during the build. During runtime tracing works as usual. Our tracing did not have any effect during build-time anyway since instrumentation.ts does not get registered – but we still ran into the uuid calls then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that makes a lot of sense 👍


/**
* Next.js-specific implementation of `startSpan` that skips span creation
* in Cache Components contexts (which render at build time).
*
* When in a Cache Components context, we execute the callback with a non-recording span
* and return early without creating an actual span, since spans don't make sense at build time.
*
* @param options - Options for starting the span
* @param callback - Callback function that receives the span
* @returns The return value of the callback
*/
export function startSpan<T>(options: StartSpanOptions, callback: (span: Span) => T): T {
if (isCacheComponentContext()) {
// Cache Components render at build time, so spans don't make sense
// Execute callback with a non-recording span (no crypto calls) and return early
// Use placeholder IDs since this span won't be sent to Sentry anyway
const nonRecordingSpan = new SentryNonRecordingSpan({
traceId: '00000000000000000000000000000000',
spanId: '0000000000000000',
});
return callback(nonRecordingSpan);
}

return coreStartSpan(options, callback);
}

/**
*
* When in a Cache Components context, we execute the callback with a non-recording span
* and return early without creating an actual span, since spans don't make sense at build time.
*
* @param options - Options for starting the span
* @param callback - Callback function that receives the span and finish function
* @returns The return value of the callback
*/
export function startSpanManual<T>(options: StartSpanOptions, callback: (span: Span, finish: () => void) => T): T {
if (isCacheComponentContext()) {
// Cache Components render at build time, so spans don't make sense
// Execute callback with a non-recording span (no crypto calls) and return early
// Use placeholder IDs since this span won't be sent to Sentry anyway
const nonRecordingSpan = new SentryNonRecordingSpan({
traceId: '00000000000000000000000000000000',
spanId: '0000000000000000',
});
return callback(nonRecordingSpan, () => nonRecordingSpan.end());
}

return coreStartSpanManual(options, callback);
}

/**
*
* When in a Cache Components context, we return a non-recording span and return early
* without creating an actual span, since spans don't make sense at build time.
*
* @param options - Options for starting the span
* @returns A non-recording span (in Cache Components context) or the created span
*/
export function startInactiveSpan(options: StartSpanOptions): Span {
if (isCacheComponentContext()) {
// Cache Components render at build time, so spans don't make sense
// Return a non-recording span (no crypto calls) and return early
// Use placeholder IDs since this span won't be sent to Sentry anyway
return new SentryNonRecordingSpan({
traceId: '00000000000000000000000000000000',
spanId: '0000000000000000',
});
}

return coreStartInactiveSpan(options);
}
3 changes: 3 additions & 0 deletions packages/nextjs/src/edge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import { distDirRewriteFramesIntegration } from './distDirRewriteFramesIntegrati
export * from '@sentry/vercel-edge';
export * from '../common';
export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error';

// Override core span methods with Next.js-specific implementations that support Cache Components
export { startSpan, startSpanManual, startInactiveSpan } from '../common/utils/nextSpan';
Copy link
Copy Markdown
Contributor

@AbhiPrasad AbhiPrasad Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more robust change here might be to set a different acs for nextjs when you initialize the sdk server-side.

That means people can still use imports from @sentry/core or @sentry/node if they want, which might be useful for us.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we would not call init in this case (of a build)

export { wrapApiHandlerWithSentry } from './wrapApiHandlerWithSentry';

export type EdgeOptions = VercelEdgeOptions;
Expand Down
3 changes: 3 additions & 0 deletions packages/nextjs/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export * from '@sentry/node';

export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error';

// Override core span methods with Next.js-specific implementations that support Cache Components
export { startSpan, startSpanManual, startInactiveSpan } from '../common/utils/nextSpan';

const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
_sentryRewriteFramesDistDir?: string;
_sentryRewritesTunnelPath?: string;
Expand Down
Loading