Skip to content

ref(nextjs): Remove all deprecated API #10549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Feb 9, 2024
Merged
16 changes: 16 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Upgrading from 7.x to 8.x

## Removal of deprecated API in `@sentry/nextjs`

The following previously deprecated API has been removed from the `@sentry/nextjs` package:

- `withSentryApi` (Replacement: `wrapApiHandlerWithSentry`)
- `withSentryAPI` (Replacement: `wrapApiHandlerWithSentry`)
- `withSentryGetServerSideProps` (Replacement: `wrapGetServerSidePropsWithSentry`)
- `withSentryGetStaticProps` (Replacement: `wrapGetStaticPropsWithSentry`)
- `withSentryServerSideGetInitialProps` (Replacement: `wrapGetInitialPropsWithSentry`)
- `withSentryServerSideAppGetInitialProps` (Replacement: `wrapAppGetInitialPropsWithSentry`)
- `withSentryServerSideDocumentGetInitialProps` (Replacement: `wrapDocumentGetInitialPropsWithSentry`)
- `withSentryServerSideErrorGetInitialProps` was renamed to `wrapErrorGetInitialPropsWithSentry`
- `nextRouterInstrumentation` (Replaced by using `browserTracingIntegration`)
- `IS_BUILD`
- `isBuild`

## Removal of Severity Enum

In v7 we deprecated the `Severity` enum in favor of using the `SeverityLevel` type. In v8 we removed the `Severity`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,7 @@ import * as Sentry from '@sentry/nextjs';
import type { NextApiRequest, NextApiResponse } from 'next';

export default function handler(req: NextApiRequest, res: NextApiResponse) {
// eslint-disable-next-line deprecation/deprecation
const transaction = Sentry.startTransaction({ name: 'test-transaction', op: 'e2e-test' });
// eslint-disable-next-line deprecation/deprecation
Sentry.getCurrentScope().setSpan(transaction);

// eslint-disable-next-line deprecation/deprecation
const span = transaction.startChild();

span.end();
transaction.end();
Sentry.startSpan({ name: 'test-span' }, span => undefined);

Sentry.flush().then(() => {
res.status(200).json({
Expand Down
1 change: 1 addition & 0 deletions packages/browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export {
browserTracingIntegration,
startBrowserTracingNavigationSpan,
startBrowserTracingPageLoadSpan,
DEFAULT_TRACE_PROPAGATION_TARGETS,
} from '@sentry-internal/tracing';
export type { RequestInstrumentationOptions } from '@sentry-internal/tracing';
export {
Expand Down
55 changes: 4 additions & 51 deletions packages/nextjs/src/client/browserTracingIntegration.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,18 @@
import {
BrowserTracing as OriginalBrowserTracing,
browserTracingIntegration as originalBrowserTracingIntegration,
defaultRequestInstrumentationOptions,
startBrowserTracingNavigationSpan,
startBrowserTracingPageLoadSpan,
} from '@sentry/react';
import type { Integration, StartSpanOptions } from '@sentry/types';
import { nextRouterInstrumentation } from '../index.client';

/**
* A custom BrowserTracing integration for Next.js.
*
* @deprecated Use `browserTracingIntegration` instead.
*/
// eslint-disable-next-line deprecation/deprecation
export class BrowserTracing extends OriginalBrowserTracing {
// eslint-disable-next-line deprecation/deprecation
public constructor(options?: ConstructorParameters<typeof OriginalBrowserTracing>[0]) {
super({
// eslint-disable-next-line deprecation/deprecation
tracingOrigins:
process.env.NODE_ENV === 'development'
? [
// Will match any URL that contains "localhost" but not "webpack.hot-update.json" - The webpack dev-server
// has cors and it doesn't like extra headers when it's accessed from a different URL.
// TODO(v8): Ideally we rework our tracePropagationTargets logic so this hack won't be necessary anymore (see issue #9764)
/^(?=.*localhost)(?!.*webpack\.hot-update\.json).*/,
/^\/(?!\/)/,
]
: // eslint-disable-next-line deprecation/deprecation
[...defaultRequestInstrumentationOptions.tracingOrigins, /^(api\/)/],
// eslint-disable-next-line deprecation/deprecation
routingInstrumentation: nextRouterInstrumentation,
...options,
});
}
}
import { nextRouterInstrumentation } from './routing/nextRoutingInstrumentation';

/**
* A custom BrowserTracing integration for Next.js.
*/
export function browserTracingIntegration(
options?: Parameters<typeof originalBrowserTracingIntegration>[0],
options: Parameters<typeof originalBrowserTracingIntegration>[0] = {},
): Integration {
const browserTracingIntegrationInstance = originalBrowserTracingIntegration({
// eslint-disable-next-line deprecation/deprecation
tracingOrigins:
process.env.NODE_ENV === 'development'
? [
// Will match any URL that contains "localhost" but not "webpack.hot-update.json" - The webpack dev-server
// has cors and it doesn't like extra headers when it's accessed from a different URL.
// TODO(v8): Ideally we rework our tracePropagationTargets logic so this hack won't be necessary anymore (see issue #9764)
/^(?=.*localhost)(?!.*webpack\.hot-update\.json).*/,
/^\/(?!\/)/,
]
: // eslint-disable-next-line deprecation/deprecation
[...defaultRequestInstrumentationOptions.tracingOrigins, /^(api\/)/],
...options,
instrumentNavigation: false,
instrumentPageLoad: false,
Expand All @@ -76,21 +33,17 @@ export function browserTracingIntegration(
// tracing integration because we need to ensure the order of execution is as follows:
// Instrumentation to start span on RSC fetch request runs -> Instrumentation to put tracing headers from active span on fetch runs
// If it were the other way around, the RSC fetch request would not receive the tracing headers from the navigation transaction.
// eslint-disable-next-line deprecation/deprecation
nextRouterInstrumentation(
() => undefined,
false,
options?.instrumentNavigation,
options.instrumentNavigation === undefined ? true : options.instrumentNavigation,
startPageloadCallback,
startNavigationCallback,
);

browserTracingIntegrationInstance.afterAllSetup(client);

// eslint-disable-next-line deprecation/deprecation
nextRouterInstrumentation(
() => undefined,
options?.instrumentPageLoad,
options.instrumentPageLoad === undefined ? true : options.instrumentPageLoad,
false,
startPageloadCallback,
startNavigationCallback,
Expand Down
41 changes: 41 additions & 0 deletions packages/nextjs/src/client/clientNormalizationIntegration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defineIntegration } from '@sentry/core';
import { rewriteFramesIntegration } from '@sentry/integrations';

export const nextjsClientStackFrameNormalizationIntegration = defineIntegration(
({ assetPrefixPath }: { assetPrefixPath: string }) => {
const rewriteFramesInstance = rewriteFramesIntegration({
// Turn `<origin>/<path>/_next/static/...` into `app:///_next/static/...`
iteratee: frame => {
try {
const { origin } = new URL(frame.filename as string);
frame.filename = frame.filename?.replace(origin, 'app://').replace(assetPrefixPath, '');
} catch (err) {
// Filename wasn't a properly formed URL, so there's nothing we can do
}

// We need to URI-decode the filename because Next.js has wildcard routes like "/users/[id].js" which show up as "/users/%5id%5.js" in Error stacktraces.
// The corresponding sources that Next.js generates have proper brackets so we also need proper brackets in the frame so that source map resolving works.
if (frame.filename && frame.filename.startsWith('app:///_next')) {
frame.filename = decodeURI(frame.filename);
}

if (
frame.filename &&
frame.filename.match(
/^app:\/\/\/_next\/static\/chunks\/(main-|main-app-|polyfills-|webpack-|framework-|framework\.)[0-9a-f]+\.js$/,
)
) {
// We don't care about these frames. It's Next.js internal code.
frame.in_app = false;
}

return frame;
},
});

return {
...rewriteFramesInstance,
name: 'NextjsClientStackFrameNormalization',
};
},
);
107 changes: 23 additions & 84 deletions packages/nextjs/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { applySdkMetadata, hasTracingEnabled } from '@sentry/core';
import type { BrowserOptions } from '@sentry/react';
import {
Integrations as OriginalIntegrations,
DEFAULT_TRACE_PROPAGATION_TARGETS,
getCurrentScope,
getDefaultIntegrations as getReactDefaultIntegrations,
init as reactInit,
Expand All @@ -11,47 +11,38 @@ import type { EventProcessor, Integration } from '@sentry/types';
import { devErrorSymbolicationEventProcessor } from '../common/devErrorSymbolicationEventProcessor';
import { getVercelEnv } from '../common/getVercelEnv';
import { browserTracingIntegration } from './browserTracingIntegration';
import { BrowserTracing } from './browserTracingIntegration';
import { rewriteFramesIntegration } from './rewriteFramesIntegration';
import { nextjsClientStackFrameNormalizationIntegration } from './clientNormalizationIntegration';
import { applyTunnelRouteOption } from './tunnelRoute';

export * from '@sentry/react';
// eslint-disable-next-line deprecation/deprecation
export { nextRouterInstrumentation } from './routing/nextRoutingInstrumentation';

export { captureUnderscoreErrorException } from '../common/_error';

/** @deprecated Import the integration function directly, e.g. `inboundFiltersIntegration()` instead of `new Integrations.InboundFilter(). */
export const Integrations = {
// eslint-disable-next-line deprecation/deprecation
...OriginalIntegrations,
BrowserTracing,
const globalWithInjectedValues = global as typeof global & {
__rewriteFramesAssetPrefixPath__: string;
};

// Previously we expected users to import `BrowserTracing` like this:
//
// import { Integrations } from '@sentry/nextjs';
// const instance = new Integrations.BrowserTracing();
//
// This makes the integrations unable to be treeshaken though. To address this, we now have
// this individual export. We now expect users to consume BrowserTracing like so:
//
// import { BrowserTracing } from '@sentry/nextjs';
// const instance = new BrowserTracing();
// eslint-disable-next-line deprecation/deprecation
export { BrowserTracing, rewriteFramesIntegration };

// Treeshakable guard to remove all code related to tracing
declare const __SENTRY_TRACING__: boolean;

/** Inits the Sentry NextJS SDK on the browser with the React SDK. */
export function init(options: BrowserOptions): void {
const opts = {
environment: getVercelEnv(true) || process.env.NODE_ENV,

tracePropagationTargets:
process.env.NODE_ENV === 'development'
? [
// Will match any URL that contains "localhost" but not "webpack.hot-update.json" - The webpack dev-server
// has cors and it doesn't like extra headers when it's accessed from a different URL.
// TODO(v8): Ideally we rework our tracePropagationTargets logic so this hack won't be necessary anymore (see issue #9764)
/^(?=.*localhost)(?!.*webpack\.hot-update\.json).*/,
/^\/(?!\/)/,
]
: [...DEFAULT_TRACE_PROPAGATION_TARGETS, /^(api\/)/],
defaultIntegrations: getDefaultIntegrations(options),
...options,
};

fixBrowserTracingIntegration(opts);
} satisfies BrowserOptions;

applyTunnelRouteOption(opts);
applySdkMetadata(opts, 'nextjs', ['nextjs', 'react']);
Expand All @@ -70,65 +61,8 @@ export function init(options: BrowserOptions): void {
}
}

// TODO v8: Remove this again
// We need to handle BrowserTracing passed to `integrations` that comes from `@sentry/tracing`, not `@sentry/nextjs` :(
function fixBrowserTracingIntegration(options: BrowserOptions): void {
const { integrations } = options;
if (!integrations) {
return;
}

if (Array.isArray(integrations)) {
options.integrations = maybeUpdateBrowserTracingIntegration(integrations);
} else {
options.integrations = defaultIntegrations => {
const userFinalIntegrations = integrations(defaultIntegrations);

return maybeUpdateBrowserTracingIntegration(userFinalIntegrations);
};
}
}

function isNewBrowserTracingIntegration(
integration: Integration,
): integration is Integration & { options?: Parameters<typeof browserTracingIntegration>[0] } {
// eslint-disable-next-line deprecation/deprecation
return !!integration.afterAllSetup && !!(integration as BrowserTracing).options;
}

function maybeUpdateBrowserTracingIntegration(integrations: Integration[]): Integration[] {
const browserTracing = integrations.find(integration => integration.name === 'BrowserTracing');

if (!browserTracing) {
return integrations;
}

// If `browserTracingIntegration()` was added, we need to force-convert it to our custom one
if (isNewBrowserTracingIntegration(browserTracing)) {
const { options } = browserTracing;
// eslint-disable-next-line deprecation/deprecation
integrations[integrations.indexOf(browserTracing)] = new BrowserTracing(options);
}

// If BrowserTracing was added, but it is not our forked version,
// replace it with our forked version with the same options
// eslint-disable-next-line deprecation/deprecation
if (!(browserTracing instanceof BrowserTracing)) {
// eslint-disable-next-line deprecation/deprecation
const options: ConstructorParameters<typeof BrowserTracing>[0] = (browserTracing as BrowserTracing).options;
// This option is overwritten by the custom integration
delete options.routingInstrumentation;
// eslint-disable-next-line deprecation/deprecation
delete options.tracingOrigins;
// eslint-disable-next-line deprecation/deprecation
integrations[integrations.indexOf(browserTracing)] = new BrowserTracing(options);
}

return integrations;
}

function getDefaultIntegrations(options: BrowserOptions): Integration[] {
const customDefaultIntegrations = [...getReactDefaultIntegrations(options), rewriteFramesIntegration()];
const customDefaultIntegrations = getReactDefaultIntegrations(options);

// This evaluates to true unless __SENTRY_TRACING__ is text-replaced with "false", in which case everything inside
// will get treeshaken away
Expand All @@ -138,6 +72,11 @@ function getDefaultIntegrations(options: BrowserOptions): Integration[] {
}
}

// This value is injected at build time, based on the output directory specified in the build config. Though a default
// is set there, we set it here as well, just in case something has gone wrong with the injection.
const assetPrefixPath = globalWithInjectedValues.__rewriteFramesAssetPrefixPath__ || '';
customDefaultIntegrations.push(nextjsClientStackFrameNormalizationIntegration({ assetPrefixPath }));

return customDefaultIntegrations;
}

Expand Down
54 changes: 0 additions & 54 deletions packages/nextjs/src/client/rewriteFramesIntegration.ts

This file was deleted.

Loading