Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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: 2 additions & 0 deletions dev-packages/e2e-tests/test-applications/nextjs-15/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,5 @@ next-env.d.ts

test-results
event-dumps

.tmp_dev_server_logs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return <p>Next 15 test app</p>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"scripts": {
"build": "next build > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)",
"clean": "npx rimraf node_modules pnpm-lock.yaml",
"clean": "npx rimraf node_modules pnpm-lock.yaml .tmp_dev_server_logs",
"test:prod": "TEST_ENV=production playwright test",
"test:dev": "TEST_ENV=development playwright test",
"test:dev-turbo": "TEST_ENV=dev-turbopack playwright test",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ if (!testEnv) {

const getStartCommand = () => {
if (testEnv === 'dev-turbopack') {
return 'pnpm next dev -p 3030 --turbopack';
return 'pnpm next dev -p 3030 --turbopack 2>&1 | tee .tmp_dev_server_logs';
}

if (testEnv === 'development') {
return 'pnpm next dev -p 3030';
return 'pnpm next dev -p 3030 2>&1 | tee .tmp_dev_server_logs';
}

if (testEnv === 'production') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { expect, test } from '@playwright/test';
import fs from 'fs';

test.only('should not print warning for async params', async ({ page }) => {
test.skip(
process.env.TEST_ENV !== 'development' && process.env.TEST_ENV !== 'dev-turbopack',
'should be skipped for non-dev mode',
);
await page.goto('/');

// If the server exits with code 1, the test will fail (see instrumentation.ts)
const devStdout = fs.readFileSync('.tmp_dev_server_logs', 'utf-8');
expect(devStdout).not.toContain('`params` should be awaited before using its properties.');

await expect(page.getByText('Next 15 test app')).toBeVisible();
});
29 changes: 29 additions & 0 deletions packages/nextjs/src/common/utils/wrapperUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
getRootSpan,
getTraceData,
httpRequestToRequestData,
isThenable,
} from '@sentry/core';
import type { IncomingMessage, ServerResponse } from 'http';
import { TRANSACTION_ATTR_SENTRY_ROUTE_BACKFILL } from '../span-attributes-with-logic-attached';
Expand Down Expand Up @@ -102,3 +103,31 @@ export async function callDataFetcherTraced<F extends (...args: any[]) => Promis
throw e;
}
}

/**
* Extracts the params and searchParams from the props object.
*
* Depending on the next version, params and searchParams may be a promise which we do not want to resolve in this function.
*/
export function maybeExtractSynchronousParamsAndSearchParams(props: unknown): {
params: Record<string, string> | undefined;
searchParams: Record<string, string> | undefined;
} {
let params =
props && typeof props === 'object' && 'params' in props
? (props.params as Record<string, string> | Promise<Record<string, string>> | undefined)
: undefined;
if (isThenable(params)) {
params = undefined;
}

let searchParams =
props && typeof props === 'object' && 'searchParams' in props
? (props.searchParams as Record<string, string> | Promise<Record<string, string>> | undefined)
: undefined;
if (isThenable(searchParams)) {
searchParams = undefined;
}

return { params, searchParams };
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavi
import { TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL } from './span-attributes-with-logic-attached';
import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils';
import { getSanitizedRequestUrl } from './utils/urls';
import { maybeExtractSynchronousParamsAndSearchParams } from './utils/wrapperUtils';
/**
* Wraps a generation function (e.g. generateMetadata) with Sentry error and performance instrumentation.
*/
Expand Down Expand Up @@ -65,9 +66,7 @@ export function wrapGenerationFunctionWithSentry<F extends (...args: any[]) => a
let data: Record<string, unknown> | undefined = undefined;
if (getClient()?.getOptions().sendDefaultPii) {
const props: unknown = args[0];
const params = props && typeof props === 'object' && 'params' in props ? props.params : undefined;
const searchParams =
props && typeof props === 'object' && 'searchParams' in props ? props.searchParams : undefined;
const { params, searchParams } = maybeExtractSynchronousParamsAndSearchParams(props);
data = { params, searchParams };
}

Expand Down
7 changes: 3 additions & 4 deletions packages/nextjs/src/common/wrapServerComponentWithSentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL } from './span-attributes-with-l
import { flushSafelyWithTimeout } from './utils/responseEnd';
import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils';
import { getSanitizedRequestUrl } from './utils/urls';
import { maybeExtractSynchronousParamsAndSearchParams } from './utils/wrapperUtils';

/**
* Wraps an `app` directory server component with Sentry error instrumentation.
Expand Down Expand Up @@ -64,10 +65,8 @@ export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>

if (getClient()?.getOptions().sendDefaultPii) {
const props: unknown = args[0];
params =
props && typeof props === 'object' && 'params' in props
? (props.params as Record<string, string>)
: undefined;
const { params: paramsFromProps } = maybeExtractSynchronousParamsAndSearchParams(props);
params = paramsFromProps;
}

isolationScope.setSDKProcessingMetadata({
Expand Down
Loading