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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

Work in this release was contributed by @xgedev, @Mohataseem89, @sebws, @G-Rath, and @gianpaj. Thank you for your contributions!
Work in this release was contributed by @xgedev, @Mohataseem89, @sebws, @G-Rath, @maximepvrt, and @gianpaj. Thank you for your contributions!

- ref(nextjs): Drop `resolve` dependency from the Next.js SDK ([#18618](https://github.com/getsentry/sentry-javascript/pull/18618))

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/bash
# To enable Sentry in Nuxt dev, it needs the sentry.server.config.mjs file from the .nuxt folder.
# First, we need to start 'nuxt dev' to generate the file, and then start 'nuxt dev' again with the NODE_OPTIONS to have Sentry enabled.

# Using a different port to avoid playwright already starting with the tests for port 3030
TEMP_PORT=3035

# 1. Start dev in background - this generates .nuxt folder
pnpm dev -p $TEMP_PORT &
DEV_PID=$!

# 2. Wait for the sentry.server.config.mjs file to appear
echo "Waiting for .nuxt/dev/sentry.server.config.mjs file..."
COUNTER=0
while [ ! -f ".nuxt/dev/sentry.server.config.mjs" ] && [ $COUNTER -lt 30 ]; do
sleep 1
((COUNTER++))
done

if [ ! -f ".nuxt/dev/sentry.server.config.mjs" ]; then
echo "ERROR: .nuxt/dev/sentry.server.config.mjs file never appeared!"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: Maybe we can also add some suggestions on what to do here, like "Rerun the the test" or something along that?

echo "This usually means the Nuxt dev server failed to start or generate the file. Try to rerun the test."
pkill -P $DEV_PID || kill $DEV_PID
exit 1
fi

# 3. Cleanup
echo "Found .nuxt/dev/sentry.server.config.mjs, stopping 'nuxt dev' process..."
pkill -P $DEV_PID || kill $DEV_PID

# Wait for port to be released
echo "Waiting for port $TEMP_PORT to be released..."
COUNTER=0
# Check if port is still in use
while lsof -i :$TEMP_PORT > /dev/null 2>&1 && [ $COUNTER -lt 10 ]; do
sleep 1
((COUNTER++))
done

if lsof -i :$TEMP_PORT > /dev/null 2>&1; then
echo "WARNING: Port $TEMP_PORT still in use after 10 seconds, proceeding anyway..."
else
echo "Port $TEMP_PORT released successfully"
fi

echo "Starting nuxt dev with Sentry server config..."

# 4. Start the actual dev command which should be used for the tests
NODE_OPTIONS='--import ./.nuxt/dev/sentry.server.config.mjs' nuxt dev
4 changes: 3 additions & 1 deletion dev-packages/e2e-tests/test-applications/nuxt-4/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
"start:import": "node --import ./.output/server/sentry.server.config.mjs .output/server/index.mjs",
"clean": "npx nuxi cleanup",
"test": "playwright test",
"test:prod": "TEST_ENV=production playwright test",
"test:dev": "TEST_ENV=development playwright test environment",
"test:build": "pnpm install && pnpm build",
"test:build-canary": "pnpm add nuxt@npm:nuxt-nightly@latest && pnpm add nitropack@npm:nitropack-nightly@latest && pnpm install --force && pnpm build",
"test:assert": "pnpm test"
"test:assert": "pnpm test:prod && pnpm test:dev"
},
"dependencies": {
"@pinia/nuxt": "^0.5.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
import { getPlaywrightConfig } from '@sentry-internal/test-utils';

const testEnv = process.env.TEST_ENV;

if (!testEnv) {
throw new Error('No test env defined');
}

const getStartCommand = () => {
if (testEnv === 'development') {
return 'bash ./nuxt-start-dev-server.bash';
}

if (testEnv === 'production') {
return 'pnpm start:import';
}

throw new Error(`Unknown test env: ${testEnv}`);
};

const config = getPlaywrightConfig({
startCommand: `pnpm start:import`,
startCommand: getStartCommand(),
});

export default config;
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/nuxt';
import { usePinia, useRuntimeConfig } from '#imports';

Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: useRuntimeConfig().public.sentry.dsn,
tunnel: `http://localhost:3031/`, // proxy server
tracesSampleRate: 1.0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/nuxt';

Sentry.init({
dsn: 'https://[email protected]/1337',
environment: 'qa', // dynamic sampling bias to keep transactions
tracesSampleRate: 1.0, // Capture 100% of the transactions
tunnel: 'http://localhost:3031/', // proxy server
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { expect, test } from '@playwright/test';
import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';
import { isDevMode } from './isDevMode';

test.describe('environment detection', async () => {
test('sets correct environment for client-side errors', async ({ page }) => {
const errorPromise = waitForError('nuxt-4', async errorEvent => {
return errorEvent?.exception?.values?.[0]?.value === 'Error thrown from Nuxt-4 E2E test app';
});

// We have to wait for networkidle in dev mode because clicking the button is a no-op otherwise (network requests are blocked during page load)
await page.goto(`/client-error`, isDevMode ? { waitUntil: 'networkidle' } : {});
await page.locator('#errorBtn').click();

const error = await errorPromise;

if (isDevMode) {
expect(error.environment).toBe('development');
} else {
expect(error.environment).toBe('production');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests use conditionals instead of separate test paths (Bugbot Rules)

Low Severity

Flagged per review rules: The tests use if (isDevMode) { ... } else { ... } conditionals to branch expected values. The rules specify to flag conditionals in tests and recommend splitting into separate test paths. While this pattern does allow sharing test logic across dev/prod environments, splitting or using test.each would make test intentions clearer and prevent one branch from hiding failures in the other.

Additional Locations (2)

Fix in Cursor Fix in Web

});

test('sets correct environment for client-side transactions', async ({ page }) => {
const transactionPromise = waitForTransaction('nuxt-4', async transactionEvent => {
return transactionEvent.transaction === '/test-param/:param()';
});

await page.goto(`/test-param/1234`);

const transaction = await transactionPromise;

if (isDevMode) {
expect(transaction.environment).toBe('development');
} else {
expect(transaction.environment).toBe('production');
}
});

test('sets correct environment for server-side errors', async ({ page }) => {
const errorPromise = waitForError('nuxt-4', async errorEvent => {
return errorEvent?.exception?.values?.[0]?.value === 'Nuxt 4 Server error';
});

await page.goto(`/fetch-server-routes`, isDevMode ? { waitUntil: 'networkidle' } : {});
await page.getByText('Fetch Server API Error', { exact: true }).click();

const error = await errorPromise;

expect(error.transaction).toBe('GET /api/server-error');

if (isDevMode) {
expect(error.environment).toBe('development');
} else {
expect(error.environment).toBe('production');
}
});

test('sets correct environment for server-side transactions', async ({ page }) => {
const transactionPromise = waitForTransaction('nuxt-4', async transactionEvent => {
return transactionEvent.transaction === 'GET /api/nitro-fetch';
});

await page.goto(`/fetch-server-routes`, isDevMode ? { waitUntil: 'networkidle' } : {});
await page.getByText('Fetch Nitro $fetch', { exact: true }).click();

const transaction = await transactionPromise;

expect(transaction.contexts.trace.op).toBe('http.server');

if (isDevMode) {
expect(transaction.environment).toBe('development');
} else {
expect(transaction.environment).toBe('production');
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const isDevMode = !!process.env.TEST_ENV && process.env.TEST_ENV.includes('development');
1 change: 1 addition & 0 deletions packages/core/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const DEFAULT_ENVIRONMENT = 'production';
export const DEV_ENVIRONMENT = 'development';
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export {
headersToDict,
httpHeadersToSpanAttributes,
} from './utils/request';
export { DEFAULT_ENVIRONMENT } from './constants';
export { DEFAULT_ENVIRONMENT, DEV_ENVIRONMENT } from './constants';
export { addBreadcrumb } from './breadcrumbs';
export { functionToStringIntegration } from './integrations/functiontostring';
// eslint-disable-next-line deprecation/deprecation
Expand Down
1 change: 1 addition & 0 deletions packages/nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@sentry/cloudflare": "10.32.1",
"@sentry/core": "10.32.1",
"@sentry/node": "10.32.1",
"@sentry/node-core": "10.32.1",
"@sentry/rollup-plugin": "^4.6.1",
"@sentry/vite-plugin": "^4.6.1",
"@sentry/vue": "10.32.1"
Expand Down
3 changes: 2 additions & 1 deletion packages/nuxt/src/client/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getDefaultIntegrations as getBrowserDefaultIntegrations, init as initBrowser } from '@sentry/browser';
import type { Client } from '@sentry/core';
import { applySdkMetadata } from '@sentry/core';
import { applySdkMetadata, DEFAULT_ENVIRONMENT, DEV_ENVIRONMENT } from '@sentry/core';
import type { SentryNuxtClientOptions } from '../common/types';

/**
Expand All @@ -12,6 +12,7 @@ export function init(options: SentryNuxtClientOptions): Client | undefined {
const sentryOptions = {
/* BrowserTracing is added later with the Nuxt client plugin */
defaultIntegrations: [...getBrowserDefaultIntegrations(options)],
environment: import.meta.dev ? DEV_ENVIRONMENT : DEFAULT_ENVIRONMENT,
...options,
};

Expand Down
12 changes: 11 additions & 1 deletion packages/nuxt/src/server/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import * as path from 'node:path';
import type { Client, Event, EventProcessor, Integration } from '@sentry/core';
import { applySdkMetadata, debug, flush, getGlobalScope, vercelWaitUntil } from '@sentry/core';
import {
applySdkMetadata,
debug,
DEFAULT_ENVIRONMENT,
DEV_ENVIRONMENT,
flush,
getGlobalScope,
vercelWaitUntil,
} from '@sentry/core';
import {
getDefaultIntegrations as getDefaultNodeIntegrations,
httpIntegration,
init as initNode,
type NodeOptions,
} from '@sentry/node';
import { isCjs } from '@sentry/node-core';
import { DEBUG_BUILD } from '../common/debug-build';
import type { SentryNuxtServerOptions } from '../common/types';

Expand All @@ -17,6 +26,7 @@ import type { SentryNuxtServerOptions } from '../common/types';
*/
export function init(options: SentryNuxtServerOptions): Client | undefined {
const sentryOptions = {
environment: !isCjs() && import.meta.dev ? DEV_ENVIRONMENT : DEFAULT_ENVIRONMENT,

This comment was marked as outdated.

Copy link
Member

@andreiborza andreiborza Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point, is this actually available in init when we --import the server file?

The tests on CI seem to pass, I wonder if that's because we run the dev command twice? Could you double-check this locally by running in only once and seeing if import.meta.dev is the expected value in the SDK's init?

Copy link
Member Author

@s1gr1d s1gr1d Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll double check that.

Edit: This is already defined at this point.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for checking!

...options,
defaultIntegrations: getNuxtDefaultIntegrations(options),
};
Expand Down
Loading