Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { expect, test } from '@playwright/test';
import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';

const packageJson = require('../package.json');

test('Should record exceptions for faulty edge server components', async ({ page }) => {
const errorEventPromise = waitForError('nextjs-app-dir', errorEvent => {
return errorEvent?.exception?.values?.[0]?.value === 'Edge Server Component Error';
Expand All @@ -27,9 +25,6 @@ test('Should record exceptions for faulty edge server components', async ({ page
});

test('Should record transaction for edge server components', async ({ page }) => {
const nextjsVersion = packageJson.dependencies.next;
const nextjsMajor = Number(nextjsVersion.split('.')[0]);

const serverComponentTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => {
return (
transactionEvent?.transaction === 'GET /edge-server-components' &&
Expand All @@ -44,12 +39,9 @@ test('Should record transaction for edge server components', async ({ page }) =>
expect(serverComponentTransaction).toBeDefined();
expect(serverComponentTransaction.contexts?.trace?.op).toBe('http.server');

// For some reason headers aren't picked up on Next.js 13 - also causing scope isolation to be broken
if (nextjsMajor >= 14) {
expect(serverComponentTransaction.request?.headers).toBeDefined();
expect(serverComponentTransaction.request?.headers).toBeDefined();

// Assert that isolation scope works properly
expect(serverComponentTransaction.tags?.['my-isolated-tag']).toBe(true);
expect(serverComponentTransaction.tags?.['my-global-scope-isolated-tag']).not.toBeDefined();
}
// Assert that isolation scope works properly
expect(serverComponentTransaction.tags?.['my-isolated-tag']).toBe(true);
expect(serverComponentTransaction.tags?.['my-global-scope-isolated-tag']).not.toBeDefined();
});
2 changes: 1 addition & 1 deletion packages/nextjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
## Compatibility

Currently, the minimum supported version of Next.js is `13.2.0`.
Currently, the minimum supported version of Next.js is `14.0.0`.

## Installation

Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"react-dom": "^18.3.1"
},
"peerDependencies": {
"next": "^13.2.0 || ^14.0 || ^15.0.0-rc.0 || ^16.0.0-0"
"next": "^14.0 || ^15.0.0-rc.0 || ^16.0.0-0"
},
"scripts": {
"build": "run-p build:transpile build:types",
Expand Down
15 changes: 0 additions & 15 deletions packages/nextjs/rollup.npm.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,5 @@ export default [
},
}),
),
...makeNPMConfigVariants(
makeBaseNPMConfig({
entrypoints: ['src/config/polyfills/perf_hooks.js'],

packageSpecificConfig: {
output: {
// Preserve the original file structure (i.e., so that everything is still relative to `src`)
entryFileNames: 'config/polyfills/[name].js',

// make it so Rollup calms down about the fact that we're combining default and named exports
exports: 'named',
},
},
}),
),
...makeOtelLoaders('./build', 'sentry-node'),
];
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,6 @@ interface NextRouter {

// Yes, yes, I know we shouldn't depend on these internals. But that's where we are at. We write the ugly code, so you don't have to.
const GLOBAL_OBJ_WITH_NEXT_ROUTER = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
// Available until 13.4.4-canary.3 - https://github.com/vercel/next.js/pull/50210
nd?: {
router?: NextRouter;
};
// Available from 13.4.4-canary.4 - https://github.com/vercel/next.js/pull/50210
next?: {
router?: NextRouter;
Expand Down Expand Up @@ -188,7 +184,7 @@ export function appRouterInstrumentNavigation(client: Client): void {
const ROUTER_AVAILABILITY_CHECK_INTERVAL_MS = 20;
const checkForRouterAvailabilityInterval = setInterval(() => {
triesToFindRouter++;
const router = GLOBAL_OBJ_WITH_NEXT_ROUTER?.next?.router ?? GLOBAL_OBJ_WITH_NEXT_ROUTER?.nd?.router;
const router = GLOBAL_OBJ_WITH_NEXT_ROUTER?.next?.router;

if (routerPatched || triesToFindRouter > MAX_TRIES_TO_FIND_ROUTER) {
clearInterval(checkForRouterAvailabilityInterval);
Expand All @@ -199,22 +195,20 @@ export function appRouterInstrumentNavigation(client: Client): void {
patchRouter(client, router, currentRouterPatchingNavigationSpanRef);

// If the router at any point gets overridden - patch again
(['nd', 'next'] as const).forEach(globalValueName => {
const globalValue = GLOBAL_OBJ_WITH_NEXT_ROUTER[globalValueName];
if (globalValue) {
GLOBAL_OBJ_WITH_NEXT_ROUTER[globalValueName] = new Proxy(globalValue, {
set(target, p, newValue) {
if (p === 'router' && typeof newValue === 'object' && newValue !== null) {
patchRouter(client, newValue, currentRouterPatchingNavigationSpanRef);
}

// @ts-expect-error we cannot possibly type this
target[p] = newValue;
return true;
},
});
}
});
const globalValue = GLOBAL_OBJ_WITH_NEXT_ROUTER.next;
if (globalValue) {
GLOBAL_OBJ_WITH_NEXT_ROUTER.next = new Proxy(globalValue, {
set(target, p, newValue) {
if (p === 'router' && typeof newValue === 'object' && newValue !== null) {
patchRouter(client, newValue, currentRouterPatchingNavigationSpanRef);
}

// @ts-expect-error we cannot possibly type this
target[p] = newValue;
return true;
},
});
}
}
}, ROUTER_AVAILABILITY_CHECK_INTERVAL_MS);
}
Expand Down
3 changes: 1 addition & 2 deletions packages/nextjs/src/common/wrapMiddlewareWithSentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ export function wrapMiddlewareWithSentry<H extends EdgeRouteHandler>(
const isTunnelRequest = isPathnameUnderSentryTunnelRoute(url.pathname, tunnelRoute);

if (isTunnelRequest) {
// Create a simple response that mimics NextResponse.next() so we don't need to import internals here
// which breaks next 13 apps
// Create a simple response that mimics NextResponse.next() so we don't need to import Next.js internals here
// https://github.com/vercel/next.js/blob/c12c9c1f78ad384270902f0890dc4cd341408105/packages/next/src/server/web/spec-extension/response.ts#L146
return new Response(null, {
status: 200,
Expand Down
27 changes: 0 additions & 27 deletions packages/nextjs/src/config/polyfills/perf_hooks.js

This file was deleted.

26 changes: 0 additions & 26 deletions packages/nextjs/src/config/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,6 @@ export function constructWebpackConfigFunction({

addOtelWarningIgnoreRule(newConfig);

// Add edge runtime polyfills when building for edge in dev mode
if (major && major === 13 && runtime === 'edge' && isDev) {
addEdgeRuntimePolyfills(newConfig, buildContext);
}

let pagesDirPath: string | undefined;
const maybePagesDirPath = path.join(projectDir, 'pages');
const maybeSrcPagesDirPath = path.join(projectDir, 'src', 'pages');
Expand Down Expand Up @@ -801,9 +796,6 @@ function resolveNextPackageDirFromDirectory(basedir: string): string | undefined
}

const POTENTIAL_REQUEST_ASYNC_STORAGE_LOCATIONS = [
// Original location of RequestAsyncStorage
// https://github.com/vercel/next.js/blob/46151dd68b417e7850146d00354f89930d10b43b/packages/next/src/client/components/request-async-storage.ts
'next/dist/client/components/request-async-storage.js',
// Introduced in Next.js 13.4.20
// https://github.com/vercel/next.js/blob/e1bc270830f2fc2df3542d4ef4c61b916c802df3/packages/next/src/client/components/request-async-storage.external.ts
'next/dist/client/components/request-async-storage.external.js',
Expand Down Expand Up @@ -892,24 +884,6 @@ function prependOrchestrionRuntimeExternals(newConfig: WebpackConfigObjectWithMo
}
}

function addEdgeRuntimePolyfills(newConfig: WebpackConfigObjectWithModuleRules, buildContext: BuildContext): void {
// Use ProvidePlugin to inject performance global only when accessed
newConfig.plugins = newConfig.plugins || [];
newConfig.plugins.push(
new buildContext.webpack.ProvidePlugin({
performance: [path.resolve(__dirname, 'polyfills', 'perf_hooks.js'), 'performance'],
}),
);

// Add module resolution aliases for problematic Node.js modules in edge runtime
newConfig.resolve = newConfig.resolve || {};
newConfig.resolve.alias = {
...newConfig.resolve.alias,
// Redirect perf_hooks imports to a polyfilled version
perf_hooks: path.resolve(__dirname, 'polyfills', 'perf_hooks.js'),
};
}

/**
* Sets up the tree-shaking flags based on the user's configuration.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/tree-shaking/
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export function init(options: NodeOptions): NodeClient | undefined {
/^\/404$/,
// App router /404 and /_not-found segments (any HTTP method)
/^(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) \/(404|_not-found)$/,
// Next.js 13 root transactions named "NextServer.getRequestHandler" containing useless tracing
// Root transactions named "NextServer.getRequestHandler" containing useless tracing

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 was not a next13-only thing

/^NextServer\.getRequestHandler$/,
// Spans flagged via TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION
// (set in `dropMiddlewareTunnelRequests` during `spanStart`)
Expand Down
120 changes: 0 additions & 120 deletions packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import '../mocks';
import * as core from '@sentry/core';
import { describe, expect, it, vi } from 'vitest';
import * as getBuildPluginOptionsModule from '../../../src/config/getBuildPluginOptions';
import * as util from '../../../src/config/util';
import {
CLIENT_SDK_CONFIG_FILE,
clientBuildContext,
Expand Down Expand Up @@ -272,125 +271,6 @@ describe('constructWebpackConfigFunction()', () => {
});
});

describe('edge runtime polyfills', () => {
it('adds polyfills only for edge runtime in dev mode on Next.js 13', async () => {
// Mock Next.js version 13 - polyfills should be added
vi.spyOn(util, 'getNextjsVersion').mockReturnValue('13.0.0');

// Test edge runtime in dev mode with Next.js 13 - should add polyfills
const edgeDevBuildContext = { ...edgeBuildContext, dev: true };
const edgeDevConfig = await materializeFinalWebpackConfig({
exportedNextConfig,
incomingWebpackConfig: serverWebpackConfig,
incomingWebpackBuildContext: edgeDevBuildContext,
});

const edgeProvidePlugin = edgeDevConfig.plugins?.find(plugin => plugin.constructor.name === 'ProvidePlugin');
expect(edgeProvidePlugin).toBeDefined();
expect(edgeDevConfig.resolve?.alias?.perf_hooks).toMatch(/perf_hooks\.js$/);

vi.restoreAllMocks();
});

it('does NOT add polyfills for edge runtime in prod mode even on Next.js 13', async () => {
// Mock Next.js version 13 - but prod mode should still not add polyfills
vi.spyOn(util, 'getNextjsVersion').mockReturnValue('13.0.0');

// Test edge runtime in prod mode - should NOT add polyfills
const edgeProdBuildContext = { ...edgeBuildContext, dev: false };
const edgeProdConfig = await materializeFinalWebpackConfig({
exportedNextConfig,
incomingWebpackConfig: serverWebpackConfig,
incomingWebpackBuildContext: edgeProdBuildContext,
});

const edgeProdProvidePlugin = edgeProdConfig.plugins?.find(plugin => plugin.constructor.name === 'ProvidePlugin');
expect(edgeProdProvidePlugin).toBeUndefined();

vi.restoreAllMocks();
});

it('does NOT add polyfills for server runtime even on Next.js 13', async () => {
// Mock Next.js version 13
vi.spyOn(util, 'getNextjsVersion').mockReturnValue('13.0.0');

// Test server runtime in dev mode - should NOT add polyfills
const serverDevBuildContext = { ...serverBuildContext, dev: true };
const serverDevConfig = await materializeFinalWebpackConfig({
exportedNextConfig,
incomingWebpackConfig: serverWebpackConfig,
incomingWebpackBuildContext: serverDevBuildContext,
});

const serverProvidePlugin = serverDevConfig.plugins?.find(plugin => plugin.constructor.name === 'ProvidePlugin');
expect(serverProvidePlugin).toBeUndefined();

vi.restoreAllMocks();
});

it('does NOT add polyfills for client runtime even on Next.js 13', async () => {
// Mock Next.js version 13
vi.spyOn(util, 'getNextjsVersion').mockReturnValue('13.0.0');

// Test client runtime in dev mode - should NOT add polyfills
const clientDevBuildContext = { ...clientBuildContext, dev: true };
const clientDevConfig = await materializeFinalWebpackConfig({
exportedNextConfig,
incomingWebpackConfig: clientWebpackConfig,
incomingWebpackBuildContext: clientDevBuildContext,
});

const clientProvidePlugin = clientDevConfig.plugins?.find(plugin => plugin.constructor.name === 'ProvidePlugin');
expect(clientProvidePlugin).toBeUndefined();

vi.restoreAllMocks();
});

it('does NOT add polyfills for edge runtime in dev mode on Next.js versions other than 13', async () => {
const edgeDevBuildContext = { ...edgeBuildContext, dev: true };

// Test with Next.js 12 - should NOT add polyfills
vi.spyOn(util, 'getNextjsVersion').mockReturnValue('12.3.0');
const edgeConfigV12 = await materializeFinalWebpackConfig({
exportedNextConfig,
incomingWebpackConfig: serverWebpackConfig,
incomingWebpackBuildContext: edgeDevBuildContext,
});
expect(edgeConfigV12.plugins?.find(plugin => plugin.constructor.name === 'ProvidePlugin')).toBeUndefined();
vi.restoreAllMocks();

// Test with Next.js 14 - should NOT add polyfills
vi.spyOn(util, 'getNextjsVersion').mockReturnValue('14.0.0');
const edgeConfigV14 = await materializeFinalWebpackConfig({
exportedNextConfig,
incomingWebpackConfig: serverWebpackConfig,
incomingWebpackBuildContext: edgeDevBuildContext,
});
expect(edgeConfigV14.plugins?.find(plugin => plugin.constructor.name === 'ProvidePlugin')).toBeUndefined();
vi.restoreAllMocks();

// Test with Next.js 15 - should NOT add polyfills
vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.0.0');
const edgeConfigV15 = await materializeFinalWebpackConfig({
exportedNextConfig,
incomingWebpackConfig: serverWebpackConfig,
incomingWebpackBuildContext: edgeDevBuildContext,
});
expect(edgeConfigV15.plugins?.find(plugin => plugin.constructor.name === 'ProvidePlugin')).toBeUndefined();
vi.restoreAllMocks();

// Test with undefined Next.js version - should NOT add polyfills
vi.spyOn(util, 'getNextjsVersion').mockReturnValue(undefined);
const edgeConfigUndefined = await materializeFinalWebpackConfig({
exportedNextConfig,
incomingWebpackConfig: serverWebpackConfig,
incomingWebpackBuildContext: edgeDevBuildContext,
});
expect(edgeConfigUndefined.plugins?.find(plugin => plugin.constructor.name === 'ProvidePlugin')).toBeUndefined();
vi.restoreAllMocks();
});
});

describe('treeshaking flags', () => {
it('does not add DefinePlugin when treeshake option is not set', async () => {
vi.spyOn(core, 'loadModule').mockImplementation(() => ({
Expand Down
Loading