diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/edge.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/edge.test.ts index a19fe3402951..ec9358e760bd 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/edge.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/edge.test.ts @@ -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'; @@ -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' && @@ -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(); }); diff --git a/packages/nextjs/README.md b/packages/nextjs/README.md index 03372a8bb6c5..ded7bdafb966 100644 --- a/packages/nextjs/README.md +++ b/packages/nextjs/README.md @@ -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 diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index b119abebdb19..060e43838f5d 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -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", diff --git a/packages/nextjs/rollup.npm.config.mjs b/packages/nextjs/rollup.npm.config.mjs index 89271a21e9d3..3582cf4574ef 100644 --- a/packages/nextjs/rollup.npm.config.mjs +++ b/packages/nextjs/rollup.npm.config.mjs @@ -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'), ]; diff --git a/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts b/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts index 57bc248410b6..f3146b55e868 100644 --- a/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts +++ b/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts @@ -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; @@ -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); @@ -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); } diff --git a/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts b/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts index a49995d1e5f2..04d5ba04ae80 100644 --- a/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts +++ b/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts @@ -41,8 +41,7 @@ export function wrapMiddlewareWithSentry( 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, diff --git a/packages/nextjs/src/config/polyfills/perf_hooks.js b/packages/nextjs/src/config/polyfills/perf_hooks.js deleted file mode 100644 index be70075641e9..000000000000 --- a/packages/nextjs/src/config/polyfills/perf_hooks.js +++ /dev/null @@ -1,27 +0,0 @@ -/* oxlint-disable sdk/no-unsafe-random-apis */ -// Polyfill for Node.js perf_hooks module in edge runtime -// This mirrors the polyfill from packages/vercel-edge/rollup.npm.config.mjs -const __sentry__timeOrigin = Date.now(); - -// Ensure performance global is available -if (typeof globalThis !== 'undefined' && globalThis.performance === undefined) { - globalThis.performance = { - timeOrigin: __sentry__timeOrigin, - now: function () { - return Date.now() - __sentry__timeOrigin; - }, - }; -} - -// Export the performance object for perf_hooks compatibility -export const performance = globalThis.performance || { - timeOrigin: __sentry__timeOrigin, - now: function () { - return Date.now() - __sentry__timeOrigin; - }, -}; - -// Default export for CommonJS compatibility -export default { - performance, -}; diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index d98472c5896d..950596b91c98 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -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'); @@ -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', @@ -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/ diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 203b0f348a97..9d56e53838ef 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -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 /^NextServer\.getRequestHandler$/, // Spans flagged via TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION // (set in `dropMiddlewareTunnelRequests` during `spanStart`) diff --git a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts index d046d1a23bfe..a31a8ca4490a 100644 --- a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts +++ b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts @@ -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, @@ -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(() => ({