Skip to content

fix(nextjs): Stop injecting release value when create release options is set to false #16695

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 1 commit into from
Jun 24, 2025
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
7 changes: 6 additions & 1 deletion packages/nextjs/src/config/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,10 @@ function addValueInjectionLoader(
): void {
const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || '';

// Check if release creation is disabled to prevent injection that breaks build determinism
const shouldCreateRelease = userSentryOptions.release?.create !== false;
const releaseToInject = releaseName && shouldCreateRelease ? releaseName : undefined;

const isomorphicValues = {
// `rewritesTunnel` set by the user in Next.js config
_sentryRewritesTunnelPath:
Expand All @@ -700,7 +704,8 @@ function addValueInjectionLoader(

// The webpack plugin's release injection breaks the `app` directory so we inject the release manually here instead.
// Having a release defined in dev-mode spams releases in Sentry so we only set one in non-dev mode
SENTRY_RELEASE: releaseName && !buildContext.dev ? { id: releaseName } : undefined,
// Only inject if release creation is not explicitly disabled (to maintain build determinism)
SENTRY_RELEASE: releaseToInject && !buildContext.dev ? { id: releaseToInject } : undefined,
_sentryBasePath: buildContext.dev ? userNextConfig.basePath : undefined,
};

Expand Down
11 changes: 8 additions & 3 deletions packages/nextjs/src/config/withSentryConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,12 @@ function getFinalConfigObject(
incomingUserNextConfigObject: NextConfigObject,
userSentryOptions: SentryBuildOptions,
): NextConfigObject {
const releaseName = userSentryOptions.release?.name ?? getSentryRelease() ?? getGitRevision();
// Only determine a release name if release creation is not explicitly disabled
// This prevents injection of Git commit hashes that break build determinism
const shouldCreateRelease = userSentryOptions.release?.create !== false;
const releaseName = shouldCreateRelease
? userSentryOptions.release?.name ?? getSentryRelease() ?? getGitRevision()
: userSentryOptions.release?.name;

if (userSentryOptions?.tunnelRoute) {
if (incomingUserNextConfigObject.output === 'export') {
Expand Down Expand Up @@ -126,8 +131,8 @@ function getFinalConfigObject(
// 1. compile: Code compilation
// 2. generate: Environment variable inlining and prerendering (We don't instrument this phase, we inline in the compile phase)
//
// We assume a single full build and reruns Webpack instrumentation in both phases.
// During the generate step it collides with Next.jss inliner
// We assume a single "full" build and reruns Webpack instrumentation in both phases.
// During the generate step it collides with Next.js's inliner
// producing malformed JS and build failures.
// We skip Sentry processing during generate to avoid this issue.
return incomingUserNextConfigObject;
Expand Down
67 changes: 67 additions & 0 deletions packages/nextjs/test/config/withSentryConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,71 @@ describe('withSentryConfig', () => {
);
});
});

describe('release injection behavior', () => {
afterEach(() => {
vi.restoreAllMocks();

// clear env to avoid leaking env vars from fixtures
delete exportedNextConfig.env;
delete process.env.SENTRY_RELEASE;
});

it('does not inject release when create is false', () => {
const sentryOptions = {
release: {
create: false,
},
};

// clear env to avoid leaking env vars from fixtures
delete exportedNextConfig.env;

const finalConfig = materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);

// Should not inject release into environment when create is false
expect(finalConfig.env).not.toHaveProperty('_sentryRelease');
});

it('injects release when create is true (default)', () => {
const sentryOptions = {
release: {
create: true,
name: 'test-release@1.0.0',
},
};

const finalConfig = materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);

// Should inject release into environment when create is true
expect(finalConfig.env).toHaveProperty('_sentryRelease', 'test-release@1.0.0');
});

it('injects release with explicit name', () => {
const sentryOptions = {
release: {
name: 'custom-release-v2.1.0',
},
};

const finalConfig = materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);

// Should inject the explicit release name
expect(finalConfig.env).toHaveProperty('_sentryRelease', 'custom-release-v2.1.0');
});

it('falls back to SENTRY_RELEASE environment variable when no explicit name provided', () => {
process.env.SENTRY_RELEASE = 'env-release-1.5.0';

const sentryOptions = {
release: {
create: true,
},
};

const finalConfig = materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);

expect(finalConfig.env).toHaveProperty('_sentryRelease', 'env-release-1.5.0');
});
});
});
Loading