From 18abe1105b65d75aad61898f9be529e55073d46b Mon Sep 17 00:00:00 2001
From: s1gr1d <32902192+s1gr1d@users.noreply.github.com>
Date: Fri, 13 Nov 2026 15:05:31 +0100
Subject: [PATCH] test(nextjs): Cover the server request link of a late trace
meta tag
The pageload waits at most 10 seconds for the trace meta tag of a resumed
shell. When the tag arrives later, the pageload links the server request
instead of continuing its trace. Unit tests cover this with fake timers. This
adds an E2E test that streams the tag after 10.5 seconds in a real build.
---
.../app/slow-stream/page.tsx | 20 +++++++++++++
.../cacheOrigin-pageloadConnection.spec.ts | 30 +++++++++++++++++++
.../app/slow-stream/page.tsx | 20 +++++++++++++
.../cacheOrigin-pageloadConnection.spec.ts | 30 +++++++++++++++++++
4 files changed, 100 insertions(+)
create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/slow-stream/page.tsx
create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/slow-stream/page.tsx
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/slow-stream/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/slow-stream/page.tsx
new file mode 100644
index 000000000000..478ac04626a6
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/slow-stream/page.tsx
@@ -0,0 +1,20 @@
+import { headers } from 'next/headers';
+import { Suspense } from 'react';
+
+// Above the pageload's 10 second wait for the trace meta tag, but short enough that the tag still
+// arrives before the pageload ends.
+const DELAY_MS = 10_500;
+
+async function SlowContent() {
+ await headers();
+ await new Promise(resolve => setTimeout(resolve, DELAY_MS));
+ return
Slow content
;
+}
+
+export default function Page() {
+ return (
+ Loading...}>
+
+
+ );
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts
index f06c4da961cd..36efdae23642 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts
@@ -212,3 +212,33 @@ test('keeps a navigation that starts while the pageload waits for the trace meta
expect(pageloadTx.contexts?.trace?.data?.['sentry.idle_span_finish_reason']).toBe('cancelled');
expect(navigationTx.contexts?.trace?.data?.['sentry.idle_span_finish_reason']).toBe('idleTimeout');
});
+
+test('links the server request when its trace meta tag arrives after the pageload stopped waiting', async ({
+ page,
+}) => {
+ test.skip(process.env.TEST_ENV !== 'production', 'Only a prerendered shell makes the trace meta tag arrive late');
+ test.setTimeout(60_000);
+
+ const pageloadTxPromise = waitForTransaction(APP, transactionEvent => {
+ return transactionEvent.transaction === '/slow-stream' && transactionEvent.contexts?.trace?.op === 'pageload';
+ });
+
+ await page.goto('/slow-stream', { waitUntil: 'commit' });
+ await expect(page.locator('#slow-content')).toBeVisible({ timeout: 20_000 });
+ const [requestTraceId, requestSpanId] = (await page
+ .locator('meta[name="sentry-trace"]')
+ .getAttribute('content'))!.split('-');
+
+ const pageloadTx = await pageloadTxPromise;
+
+ // The tag arrived after the pageload had started its own trace, so the pageload only links it.
+ expect(pageloadTx.contexts?.trace?.trace_id).not.toBe(requestTraceId);
+ expect(pageloadTx.contexts?.trace?.links).toEqual([
+ {
+ trace_id: requestTraceId,
+ span_id: requestSpanId,
+ sampled: true,
+ attributes: { [SENTRY_LINK_TYPE]: 'server_request' },
+ },
+ ]);
+});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/slow-stream/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/slow-stream/page.tsx
new file mode 100644
index 000000000000..478ac04626a6
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/slow-stream/page.tsx
@@ -0,0 +1,20 @@
+import { headers } from 'next/headers';
+import { Suspense } from 'react';
+
+// Above the pageload's 10 second wait for the trace meta tag, but short enough that the tag still
+// arrives before the pageload ends.
+const DELAY_MS = 10_500;
+
+async function SlowContent() {
+ await headers();
+ await new Promise(resolve => setTimeout(resolve, DELAY_MS));
+ return Slow content
;
+}
+
+export default function Page() {
+ return (
+ Loading...}>
+
+
+ );
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts
index 3d1512789f82..b5eadf749de5 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts
@@ -211,3 +211,33 @@ test('keeps a navigation that starts while the pageload waits for the trace meta
value: 'idleTimeout',
});
});
+
+test('links the server request when its trace meta tag arrives after the pageload stopped waiting', async ({
+ page,
+}) => {
+ test.skip(process.env.TEST_ENV !== 'production', 'Only a prerendered shell makes the trace meta tag arrive late');
+ test.setTimeout(60_000);
+
+ const pageloadSpanPromise = waitForStreamedSpan(APP, span => {
+ return span.name === '/slow-stream' && getSpanOp(span) === 'pageload' && span.is_segment;
+ });
+
+ await page.goto('/slow-stream', { waitUntil: 'commit' });
+ await expect(page.locator('#slow-content')).toBeVisible({ timeout: 20_000 });
+ const [requestTraceId, requestSpanId] = (await page
+ .locator('meta[name="sentry-trace"]')
+ .getAttribute('content'))!.split('-');
+
+ const pageloadSpan = await pageloadSpanPromise;
+
+ // The tag arrived after the pageload had started its own trace, so the pageload only links it.
+ expect(pageloadSpan.trace_id).not.toBe(requestTraceId);
+ expect(pageloadSpan.links).toEqual([
+ {
+ trace_id: requestTraceId,
+ span_id: requestSpanId,
+ sampled: true,
+ attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'server_request' } },
+ },
+ ]);
+});