Skip to content
Merged
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
9 changes: 5 additions & 4 deletions packages/nextjs/src/config/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,16 @@ function resolveNextjsPackageJson(): string | undefined {
* Checks if the current Next.js version supports the runAfterProductionCompile hook.
* This hook was introduced in Next.js 15.4.1. (https://github.com/vercel/next.js/pull/77345)
*
* @param version - version string to check.
* @returns true if Next.js version is 15.4.1 or higher
*/
export function supportsProductionCompileHook(): boolean {
const version = getNextjsVersion();
if (!version) {
export function supportsProductionCompileHook(version: string): boolean {
const versionToCheck = version;
if (!versionToCheck) {
return false;
}

const { major, minor, patch } = parseSemver(version);
const { major, minor, patch } = parseSemver(versionToCheck);

if (major === undefined || minor === undefined || patch === undefined) {
return false;
Expand Down
53 changes: 17 additions & 36 deletions packages/nextjs/src/config/withSentryConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,52 +253,33 @@ function getFinalConfigObject(
}

let nextMajor: number | undefined;
const isTurbopack = process.env.TURBOPACK;
let isTurbopackSupported = false;
if (nextJsVersion) {
const { major, minor, patch, prerelease } = parseSemver(nextJsVersion);
const { major } = parseSemver(nextJsVersion);
nextMajor = major;
const isSupportedVersion =
major !== undefined &&
minor !== undefined &&
patch !== undefined &&
(major > 15 ||
(major === 15 && minor > 3) ||
(major === 15 && minor === 3 && patch === 0 && prerelease === undefined) ||
(major === 15 && minor === 3 && patch > 0));
isTurbopackSupported = isSupportedVersion;
const isSupportedCanary =
major !== undefined &&
minor !== undefined &&
patch !== undefined &&
prerelease !== undefined &&
major === 15 &&
minor === 3 &&
patch === 0 &&
prerelease.startsWith('canary.') &&
parseInt(prerelease.split('.')[1] || '', 10) >= 28;
const supportsClientInstrumentation = isSupportedCanary || isSupportedVersion;
}

if (!supportsClientInstrumentation && isTurbopack) {
if (process.env.NODE_ENV === 'development') {
// eslint-disable-next-line no-console
console.warn(
`[@sentry/nextjs] WARNING: You are using the Sentry SDK with Turbopack (\`next dev --turbo\`). The Sentry SDK is compatible with Turbopack on Next.js version 15.3.0 or later. You are currently on ${nextJsVersion}. Please upgrade to a newer Next.js version to use the Sentry SDK with Turbopack. Note that the SDK will continue to work for non-Turbopack production builds. This warning is only about dev-mode.`,
);
} else if (process.env.NODE_ENV === 'production') {
// eslint-disable-next-line no-console
console.warn(
`[@sentry/nextjs] WARNING: You are using the Sentry SDK with Turbopack (\`next build --turbo\`). The Sentry SDK is compatible with Turbopack on Next.js version 15.3.0 or later. You are currently on ${nextJsVersion}. Please upgrade to a newer Next.js version to use the Sentry SDK with Turbopack. Note that as Turbopack is still experimental for production builds, some of the Sentry SDK features like source maps will not work. Follow this issue for progress on Sentry + Turbopack: https://github.com/getsentry/sentry-javascript/issues/8105.`,
);
}
const isTurbopack = process.env.TURBOPACK;
const isTurbopackSupported = supportsProductionCompileHook(nextJsVersion ?? '');

if (!isTurbopackSupported && isTurbopack) {
if (process.env.NODE_ENV === 'development') {
// eslint-disable-next-line no-console
console.warn(
`[@sentry/nextjs] WARNING: You are using the Sentry SDK with Turbopack (\`next dev --turbopack\`). The Sentry SDK is compatible with Turbopack on Next.js version 15.4.1 or later. You are currently on ${nextJsVersion}. Please upgrade to a newer Next.js version to use the Sentry SDK with Turbopack.`,
);
} else if (process.env.NODE_ENV === 'production') {
// eslint-disable-next-line no-console
console.warn(
`[@sentry/nextjs] WARNING: You are using the Sentry SDK with Turbopack (\`next build --turbopack\`). The Sentry SDK is compatible with Turbopack on Next.js version 15.4.1 or later. You are currently on ${nextJsVersion}. Please upgrade to a newer Next.js version to use the Sentry SDK with Turbopack.`,
);
}
}

// If not explicitly set, turbopack uses the runAfterProductionCompile hook (as there are no alternatives), webpack does not.
const shouldUseRunAfterProductionCompileHook =
userSentryOptions?.useRunAfterProductionCompileHook ?? (isTurbopack ? true : false);

if (shouldUseRunAfterProductionCompileHook && supportsProductionCompileHook()) {
if (shouldUseRunAfterProductionCompileHook && supportsProductionCompileHook(nextJsVersion ?? '')) {
if (incomingUserNextConfigObject?.compiler?.runAfterProductionCompile === undefined) {
incomingUserNextConfigObject.compiler ??= {};
incomingUserNextConfigObject.compiler.runAfterProductionCompile = async ({ distDir }) => {
Expand Down
115 changes: 22 additions & 93 deletions packages/nextjs/test/config/util.test.ts
Original file line number Diff line number Diff line change
@@ -1,169 +1,98 @@
import * as fs from 'fs';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { describe, expect, it } from 'vitest';
import * as util from '../../src/config/util';

// Mock fs to control what getNextjsVersion reads
vi.mock('fs');

describe('util', () => {
describe('supportsProductionCompileHook', () => {
afterEach(() => {
vi.restoreAllMocks();
});

describe('supported versions', () => {
it('returns true for Next.js 15.4.1', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.1' }));

const result = util.supportsProductionCompileHook();
const result = util.supportsProductionCompileHook('15.4.1');
expect(result).toBe(true);
});

it('returns true for Next.js 15.4.2', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.2' }));

expect(util.supportsProductionCompileHook()).toBe(true);
expect(util.supportsProductionCompileHook('15.4.2')).toBe(true);
});

it('returns true for Next.js 15.5.0', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.5.0' }));

expect(util.supportsProductionCompileHook()).toBe(true);
expect(util.supportsProductionCompileHook('15.5.0')).toBe(true);
});

it('returns true for Next.js 16.0.0', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '16.0.0' }));

expect(util.supportsProductionCompileHook()).toBe(true);
expect(util.supportsProductionCompileHook('16.0.0')).toBe(true);
});

it('returns true for Next.js 17.0.0', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '17.0.0' }));

expect(util.supportsProductionCompileHook()).toBe(true);
expect(util.supportsProductionCompileHook('17.0.0')).toBe(true);
});

it('returns true for supported canary versions', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.1-canary.42' }));

expect(util.supportsProductionCompileHook()).toBe(true);
expect(util.supportsProductionCompileHook('15.4.1-canary.42')).toBe(true);
});

it('returns true for supported rc versions', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.1-rc.1' }));

expect(util.supportsProductionCompileHook()).toBe(true);
expect(util.supportsProductionCompileHook('15.4.1-rc.1')).toBe(true);
});
});

describe('unsupported versions', () => {
it('returns false for Next.js 15.4.0', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.0' }));

expect(util.supportsProductionCompileHook()).toBe(false);
expect(util.supportsProductionCompileHook('15.4.0')).toBe(false);
});

it('returns false for Next.js 15.3.9', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.3.9' }));

expect(util.supportsProductionCompileHook()).toBe(false);
expect(util.supportsProductionCompileHook('15.3.9')).toBe(false);
});

it('returns false for Next.js 15.0.0', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.0.0' }));

expect(util.supportsProductionCompileHook()).toBe(false);
expect(util.supportsProductionCompileHook('15.0.0')).toBe(false);
});

it('returns false for Next.js 14.2.0', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '14.2.0' }));

expect(util.supportsProductionCompileHook()).toBe(false);
expect(util.supportsProductionCompileHook('14.2.0')).toBe(false);
});

it('returns false for unsupported canary versions', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.0-canary.42' }));

expect(util.supportsProductionCompileHook()).toBe(false);
expect(util.supportsProductionCompileHook('15.4.0-canary.42')).toBe(false);
});
});

describe('edge cases', () => {
it('returns false for invalid version strings', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: 'invalid.version' }));

expect(util.supportsProductionCompileHook()).toBe(false);
expect(util.supportsProductionCompileHook('invalid.version')).toBe(false);
});

it('handles versions with build metadata', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.1+build.123' }));

expect(util.supportsProductionCompileHook()).toBe(true);
expect(util.supportsProductionCompileHook('15.4.1+build.123')).toBe(true);
});

it('handles versions with pre-release identifiers', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.1-alpha.1' }));

expect(util.supportsProductionCompileHook()).toBe(true);
expect(util.supportsProductionCompileHook('15.4.1-alpha.1')).toBe(true);
});

it('returns false for versions missing patch number', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4' }));

expect(util.supportsProductionCompileHook()).toBe(false);
expect(util.supportsProductionCompileHook('15.4')).toBe(false);
});

it('returns false for versions missing minor number', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15' }));

expect(util.supportsProductionCompileHook()).toBe(false);
expect(util.supportsProductionCompileHook('15')).toBe(false);
});
});

describe('version boundary tests', () => {
it('returns false for 15.4.0 (just below threshold)', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.0' }));

expect(util.supportsProductionCompileHook()).toBe(false);
expect(util.supportsProductionCompileHook('15.4.0')).toBe(false);
});

it('returns true for 15.4.1 (exact threshold)', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.1' }));

expect(util.supportsProductionCompileHook()).toBe(true);
expect(util.supportsProductionCompileHook('15.4.1')).toBe(true);
});

it('returns true for 15.4.2 (just above threshold)', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.2' }));

expect(util.supportsProductionCompileHook()).toBe(true);
expect(util.supportsProductionCompileHook('15.4.2')).toBe(true);
});

it('returns false for 15.3.999 (high patch but wrong minor)', () => {
const mockReadFileSync = fs.readFileSync as any;
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.3.999' }));

expect(util.supportsProductionCompileHook()).toBe(false);
expect(util.supportsProductionCompileHook('15.3.999')).toBe(false);
});
});
});
Expand Down
Loading