diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx
new file mode 100644
index 000000000000..dbdc60adadc2
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx
@@ -0,0 +1,12 @@
+import { PropsWithChildren } from 'react';
+
+export const dynamic = 'force-dynamic';
+
+export default function Layout({ children }: PropsWithChildren<{}>) {
+ return (
+
+
DynamicLayout
+ {children}
+
+ );
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/(nested-layout)/nested-layout/[dynamic]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/(nested-layout)/nested-layout/[dynamic]/page.tsx
new file mode 100644
index 000000000000..3eaddda2a1df
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/(nested-layout)/nested-layout/[dynamic]/page.tsx
@@ -0,0 +1,15 @@
+export const dynamic = 'force-dynamic';
+
+export default async function Page() {
+ return (
+
+ );
+}
+
+export async function generateMetadata() {
+ return {
+ title: 'I am dynamic page generated metadata',
+ };
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/connected-servercomponent-trace.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/connected-servercomponent-trace.test.ts
index 3d2f29358d54..1657e94af1a4 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/connected-servercomponent-trace.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/connected-servercomponent-trace.test.ts
@@ -14,8 +14,43 @@ test('Will create a transaction with spans for every server component and metada
return span.description;
});
- expect(spanDescriptions).toContainEqual('Layout Server Component (/(nested-layout)/nested-layout)');
- expect(spanDescriptions).toContainEqual('Layout Server Component (/(nested-layout))');
- expect(spanDescriptions).toContainEqual('Page Server Component (/(nested-layout)/nested-layout)');
+ expect(spanDescriptions).toContainEqual('resolve page components');
+ expect(spanDescriptions).toContainEqual('render route (app) /nested-layout');
+ expect(spanDescriptions).toContainEqual('build component tree');
+ expect(spanDescriptions).toContainEqual('resolve root layout server component');
+ expect(spanDescriptions).toContainEqual('resolve layout server component "(nested-layout)"');
+ expect(spanDescriptions).toContainEqual('resolve layout server component "nested-layout"');
+ expect(spanDescriptions).toContainEqual('resolve page server component "/nested-layout"');
+ expect(spanDescriptions).toContainEqual('generateMetadata /(nested-layout)/nested-layout/page');
expect(spanDescriptions).toContainEqual('Page.generateMetadata (/(nested-layout)/nested-layout)');
+ expect(spanDescriptions).toContainEqual('start response');
+ expect(spanDescriptions).toContainEqual('NextNodeServer.clientComponentLoading');
+});
+
+test('Will create a transaction with spans for every server component and metadata generation functions when visiting a dynamic page', async ({
+ page,
+}) => {
+ const serverTransactionEventPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => {
+ console.log(transactionEvent?.transaction);
+ return transactionEvent?.transaction === 'GET /nested-layout/[dynamic]';
+ });
+
+ await page.goto('/nested-layout/123');
+
+ const spanDescriptions = (await serverTransactionEventPromise).spans?.map(span => {
+ return span.description;
+ });
+
+ expect(spanDescriptions).toContainEqual('resolve page components');
+ expect(spanDescriptions).toContainEqual('render route (app) /nested-layout/[dynamic]');
+ expect(spanDescriptions).toContainEqual('build component tree');
+ expect(spanDescriptions).toContainEqual('resolve root layout server component');
+ expect(spanDescriptions).toContainEqual('resolve layout server component "(nested-layout)"');
+ expect(spanDescriptions).toContainEqual('resolve layout server component "nested-layout"');
+ expect(spanDescriptions).toContainEqual('resolve layout server component "[dynamic]"');
+ expect(spanDescriptions).toContainEqual('resolve page server component "/nested-layout/[dynamic]"');
+ expect(spanDescriptions).toContainEqual('generateMetadata /(nested-layout)/nested-layout/[dynamic]/page');
+ expect(spanDescriptions).toContainEqual('Page.generateMetadata (/(nested-layout)/nested-layout/[dynamic])');
+ expect(spanDescriptions).toContainEqual('start response');
+ expect(spanDescriptions).toContainEqual('NextNodeServer.clientComponentLoading');
});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts
index eedb702715de..5aca8a0b1688 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts
@@ -70,13 +70,20 @@ test('Should set a "not_found" status on a server component span when notFound()
const transactionEvent = await serverComponentTransactionPromise;
- // Transaction should have status ok, because the http status is ok, but the server component span should be not_found
+ // Transaction should have status ok, because the http status is ok, but the render component span should be not_found
expect(transactionEvent.contexts?.trace?.status).toBe('ok');
expect(transactionEvent.spans).toContainEqual(
expect.objectContaining({
- description: 'Page Server Component (/server-component/not-found)',
- op: 'function.nextjs',
+ description: 'render route (app) /server-component/not-found',
status: 'not_found',
+ }),
+ );
+
+ // Page server component span should have the right name and attributes
+ expect(transactionEvent.spans).toContainEqual(
+ expect.objectContaining({
+ description: 'resolve page server component "/server-component/not-found"',
+ op: 'function.nextjs',
data: expect.objectContaining({
'sentry.nextjs.ssr.function.type': 'Page',
'sentry.nextjs.ssr.function.route': '/server-component/not-found',
@@ -102,13 +109,20 @@ test('Should capture an error and transaction for a app router page', async ({ p
// Error event should have the right transaction name
expect(errorEvent.transaction).toBe(`Page Server Component (/server-component/faulty)`);
- // Transaction should have status ok, because the http status is ok, but the server component span should be internal_error
+ // Transaction should have status ok, because the http status is ok, but the render component span should be internal_error
expect(transactionEvent.contexts?.trace?.status).toBe('ok');
expect(transactionEvent.spans).toContainEqual(
expect.objectContaining({
- description: 'Page Server Component (/server-component/faulty)',
- op: 'function.nextjs',
+ description: 'render route (app) /server-component/faulty',
status: 'internal_error',
+ }),
+ );
+
+ // The page server component span should have the right name and attributes
+ expect(transactionEvent.spans).toContainEqual(
+ expect.objectContaining({
+ description: 'resolve page server component "/server-component/faulty"',
+ op: 'function.nextjs',
data: expect.objectContaining({
'sentry.nextjs.ssr.function.type': 'Page',
'sentry.nextjs.ssr.function.route': '/server-component/faulty',
diff --git a/packages/nextjs/src/common/nextSpanAttributes.ts b/packages/nextjs/src/common/nextSpanAttributes.ts
index 8b9f4a9d1374..2f412d6ce7db 100644
--- a/packages/nextjs/src/common/nextSpanAttributes.ts
+++ b/packages/nextjs/src/common/nextSpanAttributes.ts
@@ -1,3 +1,5 @@
export const ATTR_NEXT_SPAN_TYPE = 'next.span_type';
export const ATTR_NEXT_SPAN_NAME = 'next.span_name';
export const ATTR_NEXT_ROUTE = 'next.route';
+export const ATTR_NEXT_SPAN_DESCRIPTION = 'next.span_description';
+export const ATTR_NEXT_SEGMENT = 'next.segment';
diff --git a/packages/nextjs/src/common/utils/tracingUtils.ts b/packages/nextjs/src/common/utils/tracingUtils.ts
index bda3049fbc78..efa3ac4fdbf6 100644
--- a/packages/nextjs/src/common/utils/tracingUtils.ts
+++ b/packages/nextjs/src/common/utils/tracingUtils.ts
@@ -1,10 +1,23 @@
-import type { PropagationContext } from '@sentry/core';
-import { debug, getActiveSpan, getRootSpan, GLOBAL_OBJ, Scope, spanToJSON, startNewTrace } from '@sentry/core';
+import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';
+import type { PropagationContext, Span, SpanAttributes } from '@sentry/core';
+import {
+ debug,
+ getActiveSpan,
+ getRootSpan,
+ GLOBAL_OBJ,
+ Scope,
+ SEMANTIC_ATTRIBUTE_SENTRY_OP,
+ spanToJSON,
+ startNewTrace,
+} from '@sentry/core';
import { DEBUG_BUILD } from '../debug-build';
+import { ATTR_NEXT_SEGMENT, ATTR_NEXT_SPAN_NAME, ATTR_NEXT_SPAN_TYPE } from '../nextSpanAttributes';
import { TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION } from '../span-attributes-with-logic-attached';
const commonPropagationContextMap = new WeakMap