diff --git a/dev-packages/e2e-tests/README.md b/dev-packages/e2e-tests/README.md
index 4b2e8acf4298..4846ced41e19 100644
--- a/dev-packages/e2e-tests/README.md
+++ b/dev-packages/e2e-tests/README.md
@@ -136,8 +136,9 @@ Apps like `cloudflare-workers-send-to-sentry` deploy the built app as a real Wor
## Runtime variants (Bun, Deno, Cloudflare)
-To test a framework on a runtime other than Node.js, add an `optionalVariants` entry to the existing test app instead of
-creating a new app. `react-router-8-framework` is the reference setup.
+To test a framework on a runtime other than Node.js, add a variant to the existing test app instead of creating a new
+app: `variants` run as required CI jobs, `optionalVariants` as optional ones. `react-router-8-framework` is the
+reference setup.
- **`RUNTIME` env var**: `node` (default), `bun`, `deno` or `cloudflare`. Read it with `getRuntime()` from
`@sentry-internal/test-utils`, which throws on any other value. Tests branch on it where the runtimes are expected to
@@ -145,7 +146,10 @@ creating a new app. `react-router-8-framework` is the reference setup.
- **SDK per runtime**: each runtime inits its own SDK, the way a user of that runtime would: Node the framework SDK
(`instrument.mjs`), Bun `@sentry/bun` (`instrument.bun.mjs`), Deno `@sentry/deno` (`instrument.deno.mjs`) and
Cloudflare `@sentry/cloudflare`. The framework SDK only provides the framework wrappers on the other runtimes, so
- values that come from its `init()` (`sdk.name`, default integrations) are Node-only.
+ values that come from its `init()` (`sdk.name`, default integrations) are Node-only. Next.js is different: it loads
+ `instrumentation.ts` on every runtime, so `nextjs-16` calls `init()` of `@sentry/nextjs` on Bun and Deno too, and
+ adds the integrations of `@sentry/bun` or `@sentry/deno` that replace the Node instrumentation those runtimes do not
+ trigger.
- **Start commands**: `playwright.config.mjs` selects the start command with `getRuntime()`. Bun and Deno use the same build
as Node and only change the start command, for example
`bun --bun --preload ./instrument.bun.mjs ./node_modules/@react-router/serve/bin.cjs ./build/server/index.js` and
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/.gitignore b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/.gitignore
deleted file mode 100644
index dd146b53d966..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/.gitignore
+++ /dev/null
@@ -1,44 +0,0 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.*
-.yarn/*
-!.yarn/patches
-!.yarn/plugins
-!.yarn/releases
-!.yarn/versions
-
-# testing
-/coverage
-
-# next.js
-/.next/
-/out/
-
-# production
-/build
-
-# misc
-.DS_Store
-*.pem
-
-# debug
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-.pnpm-debug.log*
-
-# env files (can opt-in for committing if needed)
-.env*
-
-# vercel
-.vercel
-
-# typescript
-*.tsbuildinfo
-next-env.d.ts
-
-# Sentry Config File
-.env.sentry-build-plugin
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/layout.tsx
deleted file mode 100644
index ace0c2f086b7..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/layout.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { PropsWithChildren } from 'react';
-
-export const dynamic = 'force-dynamic';
-
-export default function Layout({ children }: PropsWithChildren<{}>) {
- return (
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx
deleted file mode 100644
index dbdc60adadc2..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-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-16-bun/app/(nested-layout)/nested-layout/[dynamic]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/[dynamic]/page.tsx
deleted file mode 100644
index 3eaddda2a1df..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/[dynamic]/page.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-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-16-bun/app/(nested-layout)/nested-layout/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/layout.tsx
deleted file mode 100644
index ace0c2f086b7..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/layout.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { PropsWithChildren } from 'react';
-
-export const dynamic = 'force-dynamic';
-
-export default function Layout({ children }: PropsWithChildren<{}>) {
- return (
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/page.tsx
deleted file mode 100644
index 8077c14d23ca..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/page.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-export const dynamic = 'force-dynamic';
-
-export default function Page() {
- return Hello World!
;
-}
-
-export async function generateMetadata() {
- return {
- title: 'I am generated metadata',
- };
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/api/endpoint-behind-middleware/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/api/endpoint-behind-middleware/route.ts
deleted file mode 100644
index 2733cc918f44..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/api/endpoint-behind-middleware/route.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export function GET() {
- return Response.json({ name: 'John Doe' });
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/global-error.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/global-error.tsx
deleted file mode 100644
index 20c175015b03..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/global-error.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-'use client';
-
-import * as Sentry from '@sentry/nextjs';
-import NextError from 'next/error';
-import { useEffect } from 'react';
-
-export default function GlobalError({ error }: { error: Error & { digest?: string } }) {
- useEffect(() => {
- Sentry.captureException(error);
- }, [error]);
-
- return (
-
-
- {/* `NextError` is the default Next.js error page component. Its type
- definition requires a `statusCode` prop. However, since the App Router
- does not expose status codes for errors, we simply pass 0 to render a
- generic error message. */}
-
-
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/layout.tsx
deleted file mode 100644
index c8f9cee0b787..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/layout.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function Layout({ children }: { children: React.ReactNode }) {
- return (
-
- {children}
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/nested-rsc-error/[param]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/nested-rsc-error/[param]/page.tsx
deleted file mode 100644
index 675b248026be..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/nested-rsc-error/[param]/page.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Suspense } from 'react';
-
-export const dynamic = 'force-dynamic';
-
-export default async function Page() {
- return (
- Loading...}>
- {/* @ts-ignore */}
- ;
-
- );
-}
-
-async function Crash() {
- throw new Error('I am technically uncatchable');
- return unreachable
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/page.tsx
deleted file mode 100644
index 3204f4df69e1..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/page.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function Page() {
- return Next 16 Bun test app
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/pageload-tracing/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/pageload-tracing/layout.tsx
deleted file mode 100644
index 1f0cbe478f88..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/pageload-tracing/layout.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import { PropsWithChildren } from 'react';
-
-export const dynamic = 'force-dynamic';
-
-export default async function Layout({ children }: PropsWithChildren) {
- await new Promise(resolve => setTimeout(resolve, 500));
- return <>{children}>;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/pageload-tracing/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/pageload-tracing/page.tsx
deleted file mode 100644
index 689735d61ddf..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/pageload-tracing/page.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-export const dynamic = 'force-dynamic';
-
-export default async function Page() {
- await new Promise(resolve => setTimeout(resolve, 1000));
- return I am page 2
;
-}
-
-export async function generateMetadata() {
- (await fetch('https://example.com/', { cache: 'no-store' })).text();
-
- return {
- title: 'my title',
- };
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/beep/[two]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/beep/[two]/page.tsx
deleted file mode 100644
index f34461c2bb07..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/beep/[two]/page.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function ParameterizedPage() {
- return Dynamic page two
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/beep/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/beep/page.tsx
deleted file mode 100644
index a7d9164c8c03..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/beep/page.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function BeepPage() {
- return Beep
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/page.tsx
deleted file mode 100644
index 9fa617a22381..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/page.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function ParameterizedPage() {
- return Dynamic page one
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/static/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/static/page.tsx
deleted file mode 100644
index 16ef0482d53b..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/static/page.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function StaticPage() {
- return Static page
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/route-handler/[xoxo]/edge/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/route-handler/[xoxo]/edge/route.ts
deleted file mode 100644
index 7cd1fc7e332c..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/route-handler/[xoxo]/edge/route.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { NextResponse } from 'next/server';
-
-export const runtime = 'edge';
-export const dynamic = 'force-dynamic';
-
-export async function GET() {
- return NextResponse.json({ message: 'Hello Edge Route Handler' });
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/route-handler/[xoxo]/node/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/route-handler/[xoxo]/node/route.ts
deleted file mode 100644
index 5bc418f077aa..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/route-handler/[xoxo]/node/route.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { NextResponse } from 'next/server';
-
-export const dynamic = 'force-dynamic';
-
-export async function GET() {
- return NextResponse.json({ message: 'Hello Node Route Handler' });
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/streaming-rsc-error/[param]/client-page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/streaming-rsc-error/[param]/client-page.tsx
deleted file mode 100644
index 7b66c3fbdeef..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/streaming-rsc-error/[param]/client-page.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-'use client';
-
-import { use } from 'react';
-
-export function RenderPromise({ stringPromise }: { stringPromise: Promise }) {
- const s = use(stringPromise);
- return <>{s}>;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/streaming-rsc-error/[param]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/streaming-rsc-error/[param]/page.tsx
deleted file mode 100644
index 9531f9a42139..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/streaming-rsc-error/[param]/page.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Suspense } from 'react';
-import { RenderPromise } from './client-page';
-
-export const dynamic = 'force-dynamic';
-
-export default async function Page() {
- const crashingPromise = new Promise((_, reject) => {
- setTimeout(() => {
- reject(new Error('I am a data streaming error'));
- }, 100);
- });
-
- return (
- Loading...}>
- ;
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/third-party-filter/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/third-party-filter/page.tsx
deleted file mode 100644
index b6b4bea80def..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/third-party-filter/page.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-'use client';
-
-import * as Sentry from '@sentry/nextjs';
-
-function throwFirstPartyError(): void {
- throw new Error('first-party-error');
-}
-
-export default function Page() {
- return (
- {
- try {
- throwFirstPartyError();
- } catch (e) {
- Sentry.captureException(e);
- }
- }}
- >
- Throw First Party Error
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/instrumentation-client.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/instrumentation-client.ts
deleted file mode 100644
index 9c0366b0cea2..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/instrumentation-client.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import * as Sentry from '@sentry/nextjs';
-
-Sentry.init({
- environment: 'qa', // dynamic sampling bias to keep transactions
- dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
- tunnel: `http://localhost:3031/`, // proxy server
- tracesSampleRate: 1.0,
- integrations: [
- Sentry.thirdPartyErrorFilterIntegration({
- filterKeys: ['nextjs-16-bun-e2e'],
- behaviour: 'apply-tag-if-exclusively-contains-third-party-frames',
- }),
- ],
-});
-
-export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/instrumentation.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/instrumentation.ts
deleted file mode 100644
index 964f937c439a..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/instrumentation.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import * as Sentry from '@sentry/nextjs';
-
-export async function register() {
- if (process.env.NEXT_RUNTIME === 'nodejs') {
- await import('./sentry.server.config');
- }
-
- if (process.env.NEXT_RUNTIME === 'edge') {
- await import('./sentry.edge.config');
- }
-}
-
-export const onRequestError = Sentry.captureRequestError;
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/next.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/next.config.ts
deleted file mode 100644
index 34d17391df28..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/next.config.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { withSentryConfig } from '@sentry/nextjs/config';
-import type { NextConfig } from 'next';
-
-const nextConfig: NextConfig = {};
-
-export default withSentryConfig(nextConfig, {
- silent: true,
- applicationKey: 'nextjs-16-bun-e2e',
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/package.json
deleted file mode 100644
index 6b510830e98e..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/package.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "name": "nextjs-16-bun",
- "version": "0.1.0",
- "private": true,
- "scripts": {
- "dev": "bun --bun next dev",
- "build": "bun --bun 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 .tmp_dev_server_logs",
- "start": "bun --bun next start",
- "test:prod": "TEST_ENV=production playwright test",
- "test:build": "pnpm install && pnpm build",
- "test:assert": "pnpm test:prod"
- },
- "dependencies": {
- "@sentry/nextjs": "file:../../packed/sentry-nextjs-packed.tgz",
- "@sentry/bun": "file:../../packed/sentry-bun-packed.tgz",
- "@sentry/core": "file:../../packed/sentry-core-packed.tgz",
- "import-in-the-middle": "^2",
- "next": "16.2.11",
- "react": "19.1.0",
- "react-dom": "19.1.0",
- "require-in-the-middle": "^8"
- },
- "devDependencies": {
- "@playwright/test": "~1.63.0",
- "@sentry-internal/test-utils": "link:../../../test-utils",
- "@types/node": "^20",
- "@types/react": "^19",
- "@types/react-dom": "^19",
- "typescript": "^5"
- },
- "volta": {
- "extends": "../../package.json"
- }
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/playwright.config.mjs
deleted file mode 100644
index 28662eab2d7b..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/playwright.config.mjs
+++ /dev/null
@@ -1,25 +0,0 @@
-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 'bun --bun next dev -p 3030 2>&1 | tee .tmp_dev_server_logs';
- }
-
- if (testEnv === 'production') {
- return 'bun --bun next start -p 3030';
- }
-
- throw new Error(`Unknown test env: ${testEnv}`);
-};
-
-const config = getPlaywrightConfig({
- startCommand: getStartCommand(),
- port: 3030,
-});
-
-export default config;
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/proxy.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/proxy.ts
deleted file mode 100644
index e2d263c4d80c..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/proxy.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { getDefaultIsolationScope } from '@sentry/core';
-import * as Sentry from '@sentry/nextjs';
-import { NextResponse } from 'next/server';
-import type { NextRequest } from 'next/server';
-
-export async function proxy(request: NextRequest) {
- Sentry.setTag('my-isolated-tag', true);
- Sentry.setTag('my-global-scope-isolated-tag', getDefaultIsolationScope().getScopeData().tags['my-isolated-tag']); // We set this tag to be able to assert that the previously set tag has not leaked into the global isolation scope
-
- // Streamed spans carry no scope tags, so the tests read the isolation state from this attribute instead
- const activeSpan = Sentry.getActiveSpan();
- if (activeSpan) {
- Sentry.getRootSpan(activeSpan).setAttribute(
- 'isolation_scope.is_default',
- Sentry.getIsolationScope() === getDefaultIsolationScope(),
- );
- }
-
- if (request.headers.has('x-should-throw')) {
- throw new Error('Middleware Error');
- }
-
- if (request.headers.has('x-should-make-request')) {
- await fetch('http://localhost:3030/');
- }
-
- return NextResponse.next();
-}
-
-// See "Matching Paths" below to learn more
-export const config = {
- matcher: ['/api/endpoint-behind-middleware', '/api/endpoint-behind-faulty-middleware'],
-};
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/sentry.edge.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/sentry.edge.config.ts
deleted file mode 100644
index ad780407a5b7..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/sentry.edge.config.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import * as Sentry from '@sentry/nextjs';
-
-Sentry.init({
- environment: 'qa', // dynamic sampling bias to keep transactions
- dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
- tunnel: `http://localhost:3031/`, // proxy server
- tracesSampleRate: 1.0,
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/sentry.server.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/sentry.server.config.ts
deleted file mode 100644
index e683c5362818..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/sentry.server.config.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { bunHttpServerIntegration } from '@sentry/bun';
-import * as Sentry from '@sentry/nextjs';
-import { bunServerIntegration, fetchIntegration } from '@sentry/bun';
-
-Sentry.init({
- environment: 'qa', // dynamic sampling bias to keep transactions
- dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
- tunnel: `http://localhost:3031/`, // proxy server
- tracesSampleRate: 1.0,
- tracePropagationTargets: ['http://localhost:3030/propagation/test-outgoing-fetch/check'],
- integrations: [
- // Bun does not emit the `node:http` diagnostics channel the Node SDK uses to isolate incoming
- // requests, so each request would otherwise share one trace. Next.js emits its own server spans,
- // hence `spans: false` — this only isolates the request and resets its trace.
- bunHttpServerIntegration({ spans: false }),
- // Adding bun-specific integration here
- fetchIntegration(),
- ],
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/start-event-proxy.mjs
deleted file mode 100644
index d56d5fb74e59..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/start-event-proxy.mjs
+++ /dev/null
@@ -1,14 +0,0 @@
-import * as fs from 'fs';
-import * as path from 'path';
-import { startEventProxyServer } from '@sentry-internal/test-utils';
-
-const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json')));
-
-startEventProxyServer({
- port: 3031,
- proxyServerName: 'nextjs-16-bun',
- envelopeDumpPath: path.join(
- process.cwd(),
- `event-dumps/nextjs-16-bun-v${packageJson.dependencies.next}-${process.env.TEST_ENV}.dump`,
- ),
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/middleware.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/middleware.test.ts
deleted file mode 100644
index 2b56d36de0ce..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/middleware.test.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
-
-test('Should create a span for middleware', async ({ request }) => {
- const middlewareSpanPromise = waitForStreamedSpan('nextjs-16-bun', span => {
- return span.name === 'middleware GET' && span.is_segment;
- });
-
- const response = await request.get('/api/endpoint-behind-middleware');
- expect(await response.json()).toStrictEqual({ name: 'John Doe' });
-
- const middlewareSpan = await middlewareSpanPromise;
-
- expect(middlewareSpan.status).toBe('ok');
- expect(getSpanOp(middlewareSpan)).toBe('middleware');
- expect(middlewareSpan.attributes['sentry.segment.name.source']?.value).toBe('route');
-
- // Assert that isolation scope works properly
- expect(middlewareSpan.attributes['isolation_scope.is_default']).toEqual({ value: false, type: 'boolean' });
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/nested-rsc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/nested-rsc-error.test.ts
deleted file mode 100644
index 7ca749c0b181..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/nested-rsc-error.test.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils';
-
-test('Should capture errors from nested server components when `Sentry.captureRequestError` is added to the `onRequestError` hook', async ({
- page,
-}) => {
- const errorEventPromise = waitForError('nextjs-16-bun', errorEvent => {
- return !!errorEvent?.exception?.values?.some(value => value.value === 'I am technically uncatchable');
- });
-
- // Matched on the error's own trace so a span from an earlier spec cannot satisfy the correlation.
- const serverSpanPromise = waitForStreamedSpan('nextjs-16-bun', async span => {
- return (
- span.name === 'GET /nested-rsc-error/[param]' &&
- span.is_segment &&
- (await errorEventPromise).contexts?.trace?.trace_id === span.trace_id
- );
- });
-
- await page.goto(`/nested-rsc-error/123`);
- const errorEvent = await errorEventPromise;
- const serverSpan = await serverSpanPromise;
-
- // error event is part of the same trace as the server span
- expect(errorEvent.contexts?.trace?.trace_id).toBe(serverSpan.trace_id);
-
- expect(errorEvent.request).toMatchObject({
- headers: expect.any(Object),
- method: 'GET',
- });
-
- expect(errorEvent.contexts?.nextjs).toEqual({
- route_type: 'render',
- router_kind: 'App Router',
- router_path: '/nested-rsc-error/[param]',
- request_path: '/nested-rsc-error/123',
- });
-
- expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual({
- handled: false,
- type: 'auto.function.nextjs.on_request_error',
- });
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/pageload-tracing.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/pageload-tracing.test.ts
deleted file mode 100644
index 89ac76eadc1c..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/pageload-tracing.test.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
-
-test('App router spans should be attached to the pageload request span', async ({ page }) => {
- const serverSpanPromise = waitForStreamedSpan('nextjs-16-bun', span => {
- return span.name === 'GET /pageload-tracing' && span.is_segment;
- });
-
- const pageloadSpanPromise = waitForStreamedSpan('nextjs-16-bun', span => {
- return span.name === '/pageload-tracing' && getSpanOp(span) === 'pageload' && span.is_segment;
- });
-
- await page.goto(`/pageload-tracing`);
-
- const [serverSpan, pageloadSpan] = await Promise.all([serverSpanPromise, pageloadSpanPromise]);
-
- expect(pageloadSpan.trace_id).toBeTruthy();
- expect(serverSpan.trace_id).toBe(pageloadSpan.trace_id);
-});
-
-// Bun runtime does not populate HTTP request headers as span attributes
-// because the OTel HTTP instrumentation does not extract headers when running on Bun.
-// This is a known behavioral difference from Node.js.
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/parameterized-routes.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/parameterized-routes.test.ts
deleted file mode 100644
index 27afcc6c3cf3..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/parameterized-routes.test.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
-
-test('should create a parameterized pageload span when the `app` directory is used', async ({ page }) => {
- const spanPromise = waitForStreamedSpan('nextjs-16-bun', span => {
- return span.name === '/parameterized/:one' && getSpanOp(span) === 'pageload' && span.is_segment;
- });
-
- await page.goto(`/parameterized/cappuccino`);
-
- const span = await spanPromise;
-
- expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/));
- expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/));
- expect(span.attributes).toMatchObject({
- 'sentry.op': { value: 'pageload', type: 'string' },
- 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' },
- 'sentry.segment.name.source': { value: 'route', type: 'string' },
- 'sentry.environment': { value: 'qa', type: 'string' },
- 'react.version': { value: expect.any(String), type: 'string' },
- });
-});
-
-test('should create a span named after the static route when the `app` directory is used', async ({ page }) => {
- const spanPromise = waitForStreamedSpan('nextjs-16-bun', span => {
- return span.name === '/parameterized/static' && getSpanOp(span) === 'pageload' && span.is_segment;
- });
-
- await page.goto(`/parameterized/static`);
-
- const span = await spanPromise;
-
- expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/));
- expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/));
- expect(span.attributes).toMatchObject({
- 'sentry.op': { value: 'pageload', type: 'string' },
- 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' },
- 'sentry.segment.name.source': { value: 'route', type: 'string' },
- 'sentry.environment': { value: 'qa', type: 'string' },
- 'react.version': { value: expect.any(String), type: 'string' },
- 'url.template': { value: '/parameterized/static', type: 'string' },
- });
-});
-
-test('should create a partially parameterized pageload span when the `app` directory is used', async ({ page }) => {
- const spanPromise = waitForStreamedSpan('nextjs-16-bun', span => {
- return span.name === '/parameterized/:one/beep' && getSpanOp(span) === 'pageload' && span.is_segment;
- });
-
- await page.goto(`/parameterized/cappuccino/beep`);
-
- const span = await spanPromise;
-
- expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/));
- expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/));
- expect(span.attributes).toMatchObject({
- 'sentry.op': { value: 'pageload', type: 'string' },
- 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' },
- 'sentry.segment.name.source': { value: 'route', type: 'string' },
- 'sentry.environment': { value: 'qa', type: 'string' },
- 'react.version': { value: expect.any(String), type: 'string' },
- });
-});
-
-test('should create a nested parameterized pageload span when the `app` directory is used', async ({ page }) => {
- const spanPromise = waitForStreamedSpan('nextjs-16-bun', span => {
- return span.name === '/parameterized/:one/beep/:two' && getSpanOp(span) === 'pageload' && span.is_segment;
- });
-
- await page.goto(`/parameterized/cappuccino/beep/espresso`);
-
- const span = await spanPromise;
-
- expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/));
- expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/));
- expect(span.attributes).toMatchObject({
- 'sentry.op': { value: 'pageload', type: 'string' },
- 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' },
- 'sentry.segment.name.source': { value: 'route', type: 'string' },
- 'sentry.environment': { value: 'qa', type: 'string' },
- 'react.version': { value: expect.any(String), type: 'string' },
- });
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/route-handler.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/route-handler.test.ts
deleted file mode 100644
index da51193cb345..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/route-handler.test.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import test, { expect } from '@playwright/test';
-import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
-
-test('Should create a span for node route handlers', async ({ request }) => {
- const routehandlerSpanPromise = waitForStreamedSpan('nextjs-16-bun', span => {
- return span.name === 'GET /route-handler/[xoxo]/node' && span.is_segment;
- });
-
- const response = await request.get('/route-handler/123/node', { headers: { 'x-charly': 'gomez' } });
- expect(await response.json()).toStrictEqual({ message: 'Hello Node Route Handler' });
-
- const routehandlerSpan = await routehandlerSpanPromise;
-
- expect(routehandlerSpan.status).toBe('ok');
- expect(getSpanOp(routehandlerSpan)).toBe('http.server');
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/server-components.test.ts
deleted file mode 100644
index c674845c4234..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/server-components.test.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils';
-
-// Next.js emits these spans itself. The SDK attaches no op, description or function name to
-// them, so asserting `undefined` pins that they stay untouched.
-const nextjsSpan = { op: undefined, description: undefined, codeFunctionName: undefined };
-
-test('Sends a span for a request to app router with URL', async ({ page }) => {
- const spansPromise = collectStreamedSpansUntilSegment(
- 'nextjs-16-bun',
- span =>
- span.name === 'GET /parameterized/[one]/beep/[two]' &&
- String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'),
- );
-
- await page.goto('/parameterized/1337/beep/42');
-
- const spans = await spansPromise;
- const segmentSpan = spans.find(
- span =>
- span.name === 'GET /parameterized/[one]/beep/[two]' &&
- span.is_segment &&
- String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'),
- )!;
-
- expect(segmentSpan.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/));
- expect(segmentSpan.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/));
- expect(segmentSpan.status).toBe('ok');
- expect(segmentSpan.attributes).toMatchObject({
- 'sentry.op': { value: 'http.server', type: 'string' },
- 'sentry.origin': { value: 'auto', type: 'string' },
- 'sentry.sample_rate': { value: 1, type: 'integer' },
- 'sentry.segment.name.source': { value: 'route', type: 'string' },
- 'http.method': { value: 'GET', type: 'string' },
- 'http.response.status_code': { value: 200, type: 'integer' },
- 'http.route': { value: '/parameterized/[one]/beep/[two]', type: 'string' },
- 'http.status_code': { value: 200, type: 'integer' },
- 'http.target': { value: '/parameterized/1337/beep/42', type: 'string' },
- 'sentry.kind': { value: 'server', type: 'string' },
- 'next.route': { value: '/parameterized/[one]/beep/[two]', type: 'string' },
- });
-
- // No child span should share the segment span's name
- expect(spans.filter(span => !span.is_segment && span.name === segmentSpan.name)).toHaveLength(0);
-});
-
-test('Will create spans for every server component and metadata generation functions when visiting a page', async ({
- page,
-}) => {
- const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-bun', 'GET /nested-layout');
-
- await page.goto('/nested-layout');
-
- const fullSpans = await spansPromise;
- const spans = fullSpans.map(span => ({
- name: span.name,
- op: getSpanOp(span),
- description: span.attributes['sentry.description']?.value,
- codeFunctionName: span.attributes['code.function.name']?.value,
- }));
-
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout' });
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' });
- // Server component spans: the name is the low-cardinality `code.function.name`, and the
- // segment each one resolved for is on the description.
- expect(spans).toContainEqual({
- name: 'Layout',
- op: 'function',
- description: 'resolve root layout server component',
- codeFunctionName: 'Layout',
- });
- expect(spans).toContainEqual({
- name: 'Layout',
- op: 'function',
- description: 'resolve layout server component "(nested-layout)"',
- codeFunctionName: 'Layout',
- });
- expect(spans).toContainEqual({
- name: 'Layout',
- op: 'function',
- description: 'resolve layout server component "nested-layout"',
- codeFunctionName: 'Layout',
- });
- expect(spans).toContainEqual({
- name: 'Page',
- op: 'function',
- description: 'resolve page server component "/nested-layout"',
- codeFunctionName: 'Page',
- });
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'generateMetadata /(nested-layout)/nested-layout/page' });
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' });
-});
-
-test('Will create spans for every server component and metadata generation functions when visiting a dynamic page', async ({
- page,
-}) => {
- const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-bun', 'GET /nested-layout/[dynamic]');
-
- await page.goto('/nested-layout/123');
-
- const fullSpans = await spansPromise;
- const spans = fullSpans.map(span => ({
- name: span.name,
- op: getSpanOp(span),
- description: span.attributes['sentry.description']?.value,
- codeFunctionName: span.attributes['code.function.name']?.value,
- }));
-
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'resolve page components' });
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout/[dynamic]' });
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' });
- // Server component spans: the name is the low-cardinality `code.function.name`, and the
- // segment each one resolved for is on the description.
- expect(spans).toContainEqual({
- name: 'Layout',
- op: 'function',
- description: 'resolve root layout server component',
- codeFunctionName: 'Layout',
- });
- expect(spans).toContainEqual({
- name: 'Layout',
- op: 'function',
- description: 'resolve layout server component "(nested-layout)"',
- codeFunctionName: 'Layout',
- });
- expect(spans).toContainEqual({
- name: 'Layout',
- op: 'function',
- description: 'resolve layout server component "nested-layout"',
- codeFunctionName: 'Layout',
- });
- expect(spans).toContainEqual({
- name: 'Layout',
- op: 'function',
- description: 'resolve layout server component "[dynamic]"',
- codeFunctionName: 'Layout',
- });
- expect(spans).toContainEqual({
- name: 'Page',
- op: 'function',
- description: 'resolve page server component "/nested-layout/[dynamic]"',
- codeFunctionName: 'Page',
- });
- expect(spans).toContainEqual({
- ...nextjsSpan,
- name: 'generateMetadata /(nested-layout)/nested-layout/[dynamic]/page',
- });
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' });
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/streaming-rsc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/streaming-rsc-error.test.ts
deleted file mode 100644
index 7b5482d4d8e2..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/streaming-rsc-error.test.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils';
-
-test('Should capture errors for crashing streaming promises in server components when `Sentry.captureRequestError` is added to the `onRequestError` hook', async ({
- page,
-}) => {
- const errorEventPromise = waitForError('nextjs-16-bun', errorEvent => {
- return !!errorEvent?.exception?.values?.some(value => value.value === 'I am a data streaming error');
- });
-
- // Matched on the error's own trace so a span from an earlier spec cannot satisfy the correlation.
- const serverSpanPromise = waitForStreamedSpan('nextjs-16-bun', async span => {
- return (
- span.name === 'GET /streaming-rsc-error/[param]' &&
- span.is_segment &&
- (await errorEventPromise).contexts?.trace?.trace_id === span.trace_id
- );
- });
-
- // The streaming RSC error can interrupt the HTTP response, causing the navigation to reject
- // (e.g. net::ERR_ABORTED) even though the error and transaction are still captured.
- await page.goto(`/streaming-rsc-error/123`).catch(() => {});
- const errorEvent = await errorEventPromise;
- const serverSpan = await serverSpanPromise;
-
- // error event is part of the same trace as the server span
- expect(errorEvent.contexts?.trace?.trace_id).toBe(serverSpan.trace_id);
-
- expect(errorEvent.request).toMatchObject({
- headers: expect.any(Object),
- method: 'GET',
- });
-
- expect(errorEvent.contexts?.nextjs).toEqual({
- route_type: 'render',
- router_kind: 'App Router',
- router_path: '/streaming-rsc-error/[param]',
- request_path: '/streaming-rsc-error/123',
- });
-
- expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual({
- handled: false,
- type: 'auto.function.nextjs.on_request_error',
- });
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/third-party-filter.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/third-party-filter.test.ts
deleted file mode 100644
index 4de1433597b5..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/third-party-filter.test.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import test, { expect } from '@playwright/test';
-import { waitForError } from '@sentry-internal/test-utils';
-
-const isWebpackDev = process.env.TEST_ENV === 'development-webpack';
-
-test('First-party error should not be tagged as third-party with exclusively-contains mode', async ({ page }) => {
- test.skip(isWebpackDev, 'Only relevant for Turbopack builds');
-
- const errorPromise = waitForError('nextjs-16-bun', errorEvent => {
- return errorEvent?.exception?.values?.some(value => value.value === 'first-party-error') ?? false;
- });
-
- await page.goto('/third-party-filter');
- await page.locator('#first-party-error-btn').click();
-
- const errorEvent = await errorPromise;
-
- expect(errorEvent.exception?.values?.[0]?.value).toBe('first-party-error');
-
- // In production, TEST_ENV=production is shared by both turbopack and webpack variants.
- // Only assert when the build is actually turbopack.
- if (errorEvent.tags?.turbopack) {
- // The integration uses `apply-tag-if-exclusively-contains-third-party-frames` which
- // only tags errors if ALL frames are third-party. A first-party error with React frames
- // should not be tagged because the first-party frames have metadata.
- expect(errorEvent.tags?.third_party_code).toBeUndefined();
- }
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tsconfig.json b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tsconfig.json
deleted file mode 100644
index cc9ed39b5aa2..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tsconfig.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "compilerOptions": {
- "target": "ES2017",
- "lib": ["dom", "dom.iterable", "esnext"],
- "allowJs": true,
- "skipLibCheck": true,
- "strict": true,
- "noEmit": true,
- "esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "jsx": "react-jsx",
- "incremental": true,
- "plugins": [
- {
- "name": "next"
- }
- ],
- "paths": {
- "@/*": ["./*"]
- }
- },
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts", "**/*.mts"],
- "exclude": ["node_modules"]
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/.gitignore b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/.gitignore
deleted file mode 100644
index dd146b53d966..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/.gitignore
+++ /dev/null
@@ -1,44 +0,0 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.*
-.yarn/*
-!.yarn/patches
-!.yarn/plugins
-!.yarn/releases
-!.yarn/versions
-
-# testing
-/coverage
-
-# next.js
-/.next/
-/out/
-
-# production
-/build
-
-# misc
-.DS_Store
-*.pem
-
-# debug
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-.pnpm-debug.log*
-
-# env files (can opt-in for committing if needed)
-.env*
-
-# vercel
-.vercel
-
-# typescript
-*.tsbuildinfo
-next-env.d.ts
-
-# Sentry Config File
-.env.sentry-build-plugin
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/layout.tsx
deleted file mode 100644
index ace0c2f086b7..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/layout.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { PropsWithChildren } from 'react';
-
-export const dynamic = 'force-dynamic';
-
-export default function Layout({ children }: PropsWithChildren<{}>) {
- return (
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx
deleted file mode 100644
index dbdc60adadc2..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-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-16-cf-workers/app/(nested-layout)/nested-layout/[dynamic]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/[dynamic]/page.tsx
deleted file mode 100644
index 3eaddda2a1df..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/[dynamic]/page.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-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-16-cf-workers/app/(nested-layout)/nested-layout/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/layout.tsx
deleted file mode 100644
index ace0c2f086b7..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/layout.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { PropsWithChildren } from 'react';
-
-export const dynamic = 'force-dynamic';
-
-export default function Layout({ children }: PropsWithChildren<{}>) {
- return (
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/page.tsx
deleted file mode 100644
index 8077c14d23ca..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/page.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-export const dynamic = 'force-dynamic';
-
-export default function Page() {
- return Hello World!
;
-}
-
-export async function generateMetadata() {
- return {
- title: 'I am generated metadata',
- };
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/endpoint-behind-middleware/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/endpoint-behind-middleware/route.ts
deleted file mode 100644
index 2733cc918f44..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/endpoint-behind-middleware/route.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export function GET() {
- return Response.json({ name: 'John Doe' });
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/test-success/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/test-success/route.ts
deleted file mode 100644
index 26d85db4ac28..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/test-success/route.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { NextResponse } from 'next/server';
-
-export const dynamic = 'force-dynamic';
-
-export async function GET() {
- return NextResponse.json({ success: true });
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/favicon.ico b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/favicon.ico
deleted file mode 100644
index 718d6fea4835..000000000000
Binary files a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/favicon.ico and /dev/null differ
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/global-error.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/global-error.tsx
deleted file mode 100644
index 20c175015b03..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/global-error.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-'use client';
-
-import * as Sentry from '@sentry/nextjs';
-import NextError from 'next/error';
-import { useEffect } from 'react';
-
-export default function GlobalError({ error }: { error: Error & { digest?: string } }) {
- useEffect(() => {
- Sentry.captureException(error);
- }, [error]);
-
- return (
-
-
- {/* `NextError` is the default Next.js error page component. Its type
- definition requires a `statusCode` prop. However, since the App Router
- does not expose status codes for errors, we simply pass 0 to render a
- generic error message. */}
-
-
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/isr-test/[product]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/isr-test/[product]/page.tsx
deleted file mode 100644
index cd1e085e2763..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/isr-test/[product]/page.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-export const revalidate = 60; // ISR: revalidate every 60 seconds
-export const dynamicParams = true; // Allow dynamic params beyond generateStaticParams
-
-export async function generateStaticParams(): Promise> {
- return [{ product: 'laptop' }, { product: 'phone' }, { product: 'tablet' }];
-}
-
-export default async function ISRProductPage({ params }: { params: Promise<{ product: string }> }) {
- const { product } = await params;
-
- return (
-
-
ISR Product: {product}
-
{product}
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/isr-test/static/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/isr-test/static/page.tsx
deleted file mode 100644
index f49605bd9da4..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/isr-test/static/page.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-export const revalidate = 60; // ISR: revalidate every 60 seconds
-export const dynamicParams = true;
-
-export async function generateStaticParams(): Promise {
- return [];
-}
-
-export default function ISRStaticPage() {
- return (
-
-
ISR Static Page
-
static-isr
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/layout.tsx
deleted file mode 100644
index c8f9cee0b787..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/layout.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function Layout({ children }: { children: React.ReactNode }) {
- return (
-
- {children}
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/metrics/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/metrics/page.tsx
deleted file mode 100644
index fdb7bc0a40a7..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/metrics/page.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-'use client';
-
-import * as Sentry from '@sentry/nextjs';
-
-export default function Page() {
- const handleClick = async () => {
- Sentry.metrics.count('test.page.count', 1, {
- attributes: {
- page: '/metrics',
- 'random.attribute': 'Apples',
- },
- });
- Sentry.metrics.distribution('test.page.distribution', 100, {
- attributes: {
- page: '/metrics',
- 'random.attribute': 'Manzanas',
- },
- });
- Sentry.metrics.gauge('test.page.gauge', 200, {
- attributes: {
- page: '/metrics',
- 'random.attribute': 'Mele',
- },
- });
- await fetch('/metrics/route-handler');
- };
-
- return (
-
-
Metrics page
- Emit
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/metrics/route-handler/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/metrics/route-handler/route.ts
deleted file mode 100644
index 84e81960f9c9..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/metrics/route-handler/route.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import * as Sentry from '@sentry/nextjs';
-
-export const GET = async () => {
- Sentry.metrics.count('test.route.handler.count', 1, {
- attributes: {
- endpoint: '/metrics/route-handler',
- 'random.attribute': 'Potatoes',
- },
- });
- Sentry.metrics.distribution('test.route.handler.distribution', 100, {
- attributes: {
- endpoint: '/metrics/route-handler',
- 'random.attribute': 'Patatas',
- },
- });
- Sentry.metrics.gauge('test.route.handler.gauge', 200, {
- attributes: {
- endpoint: '/metrics/route-handler',
- 'random.attribute': 'Patate',
- },
- });
- return Response.json({ message: 'Bueno' });
-};
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/nested-rsc-error/[param]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/nested-rsc-error/[param]/page.tsx
deleted file mode 100644
index 675b248026be..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/nested-rsc-error/[param]/page.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Suspense } from 'react';
-
-export const dynamic = 'force-dynamic';
-
-export default async function Page() {
- return (
- Loading...}>
- {/* @ts-ignore */}
- ;
-
- );
-}
-
-async function Crash() {
- throw new Error('I am technically uncatchable');
- return unreachable
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/non-isr-test/[item]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/non-isr-test/[item]/page.tsx
deleted file mode 100644
index e0bafdb24181..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/non-isr-test/[item]/page.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-// No generateStaticParams - this is NOT an ISR page
-export default async function NonISRPage({ params }: { params: Promise<{ item: string }> }) {
- const { item } = await params;
-
- return (
-
-
Non-ISR Dynamic Page: {item}
-
{item}
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/page.tsx
deleted file mode 100644
index 2bc0a407a355..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/page.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function Page() {
- return Next 16 test app
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/pageload-tracing/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/pageload-tracing/layout.tsx
deleted file mode 100644
index 1f0cbe478f88..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/pageload-tracing/layout.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import { PropsWithChildren } from 'react';
-
-export const dynamic = 'force-dynamic';
-
-export default async function Layout({ children }: PropsWithChildren) {
- await new Promise(resolve => setTimeout(resolve, 500));
- return <>{children}>;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/pageload-tracing/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/pageload-tracing/page.tsx
deleted file mode 100644
index 689735d61ddf..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/pageload-tracing/page.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-export const dynamic = 'force-dynamic';
-
-export default async function Page() {
- await new Promise(resolve => setTimeout(resolve, 1000));
- return I am page 2
;
-}
-
-export async function generateMetadata() {
- (await fetch('https://example.com/', { cache: 'no-store' })).text();
-
- return {
- title: 'my title',
- };
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/beep/[two]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/beep/[two]/page.tsx
deleted file mode 100644
index f34461c2bb07..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/beep/[two]/page.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function ParameterizedPage() {
- return Dynamic page two
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/beep/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/beep/page.tsx
deleted file mode 100644
index a7d9164c8c03..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/beep/page.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function BeepPage() {
- return Beep
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/page.tsx
deleted file mode 100644
index 9fa617a22381..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/page.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function ParameterizedPage() {
- return Dynamic page one
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/static/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/static/page.tsx
deleted file mode 100644
index 16ef0482d53b..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/static/page.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function StaticPage() {
- return Static page
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/prefetching/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/prefetching/page.tsx
deleted file mode 100644
index 4cb811ecf1b4..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/prefetching/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import Link from 'next/link';
-
-export default function Page() {
- return (
-
- link
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/prefetching/to-be-prefetched/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/prefetching/to-be-prefetched/page.tsx
deleted file mode 100644
index 83aac90d65cf..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/prefetching/to-be-prefetched/page.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-export const dynamic = 'force-dynamic';
-
-export default function Page() {
- return Hello
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/redirect/destination/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/redirect/destination/page.tsx
deleted file mode 100644
index 5583d36b04b0..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/redirect/destination/page.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function RedirectDestinationPage() {
- return (
-
-
Redirect Destination
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/redirect/origin/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/redirect/origin/page.tsx
deleted file mode 100644
index 52615e0a054b..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/redirect/origin/page.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { redirect } from 'next/navigation';
-
-async function redirectAction() {
- 'use server';
-
- redirect('/redirect/destination');
-}
-
-export default function RedirectOriginPage() {
- return (
- <>
- {/* @ts-ignore */}
-
- >
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/route-handler/[xoxo]/edge/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/route-handler/[xoxo]/edge/route.ts
deleted file mode 100644
index 7cd1fc7e332c..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/route-handler/[xoxo]/edge/route.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { NextResponse } from 'next/server';
-
-export const runtime = 'edge';
-export const dynamic = 'force-dynamic';
-
-export async function GET() {
- return NextResponse.json({ message: 'Hello Edge Route Handler' });
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/route-handler/[xoxo]/node/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/route-handler/[xoxo]/node/route.ts
deleted file mode 100644
index 5bc418f077aa..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/route-handler/[xoxo]/node/route.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { NextResponse } from 'next/server';
-
-export const dynamic = 'force-dynamic';
-
-export async function GET() {
- return NextResponse.json({ message: 'Hello Node Route Handler' });
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/streaming-rsc-error/[param]/client-page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/streaming-rsc-error/[param]/client-page.tsx
deleted file mode 100644
index 7b66c3fbdeef..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/streaming-rsc-error/[param]/client-page.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-'use client';
-
-import { use } from 'react';
-
-export function RenderPromise({ stringPromise }: { stringPromise: Promise }) {
- const s = use(stringPromise);
- return <>{s}>;
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/streaming-rsc-error/[param]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/streaming-rsc-error/[param]/page.tsx
deleted file mode 100644
index 9531f9a42139..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/streaming-rsc-error/[param]/page.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Suspense } from 'react';
-import { RenderPromise } from './client-page';
-
-export const dynamic = 'force-dynamic';
-
-export default async function Page() {
- const crashingPromise = new Promise((_, reject) => {
- setTimeout(() => {
- reject(new Error('I am a data streaming error'));
- }, 100);
- });
-
- return (
- Loading...}>
- ;
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/eslint.config.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/eslint.config.mjs
deleted file mode 100644
index 60f7af38f6c2..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/eslint.config.mjs
+++ /dev/null
@@ -1,19 +0,0 @@
-import { dirname } from 'path';
-import { fileURLToPath } from 'url';
-import { FlatCompat } from '@eslint/eslintrc';
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-
-const compat = new FlatCompat({
- baseDirectory: __dirname,
-});
-
-const eslintConfig = [
- ...compat.extends('next/core-web-vitals', 'next/typescript'),
- {
- ignores: ['node_modules/**', '.next/**', 'out/**', 'build/**', 'next-env.d.ts'],
- },
-];
-
-export default eslintConfig;
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/instrumentation-client.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/instrumentation-client.ts
deleted file mode 100644
index a170a388c465..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/instrumentation-client.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import * as Sentry from '@sentry/nextjs';
-import type { Log } from '@sentry/nextjs';
-
-Sentry.init({
- environment: 'qa', // dynamic sampling bias to keep transactions
- dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
- tunnel: `http://localhost:3031/`, // proxy server
- tracesSampleRate: 1.0,
- // Verify Log type is available
- beforeSendLog(log: Log) {
- return log;
- },
-});
-
-export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/instrumentation.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/instrumentation.ts
deleted file mode 100644
index 964f937c439a..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/instrumentation.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import * as Sentry from '@sentry/nextjs';
-
-export async function register() {
- if (process.env.NEXT_RUNTIME === 'nodejs') {
- await import('./sentry.server.config');
- }
-
- if (process.env.NEXT_RUNTIME === 'edge') {
- await import('./sentry.edge.config');
- }
-}
-
-export const onRequestError = Sentry.captureRequestError;
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/middleware.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/middleware.ts
deleted file mode 100644
index a6d31cc8c66f..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/middleware.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { getDefaultIsolationScope } from '@sentry/core';
-import * as Sentry from '@sentry/nextjs';
-import { NextResponse } from 'next/server';
-import type { NextRequest } from 'next/server';
-
-export async function middleware(request: NextRequest) {
- Sentry.setTag('my-isolated-tag', true);
- Sentry.setTag('my-global-scope-isolated-tag', getDefaultIsolationScope().getScopeData().tags['my-isolated-tag']); // We set this tag to be able to assert that the previously set tag has not leaked into the global isolation scope
-
- // Streamed spans carry no scope tags, so the tests read the isolation state from this attribute instead
- const activeSpan = Sentry.getActiveSpan();
- if (activeSpan) {
- Sentry.getRootSpan(activeSpan).setAttribute(
- 'isolation_scope.is_default',
- Sentry.getIsolationScope() === getDefaultIsolationScope(),
- );
- }
-
- if (request.headers.has('x-should-throw')) {
- throw new Error('Middleware Error');
- }
-
- if (request.headers.has('x-should-make-request')) {
- await fetch('http://localhost:3030/');
- }
-
- return NextResponse.next();
-}
-
-// See "Matching Paths" below to learn more
-export const config = {
- matcher: ['/api/endpoint-behind-middleware', '/api/endpoint-behind-faulty-middleware'],
-};
-
-export const runtime = 'experimental-edge';
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/next.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/next.config.ts
deleted file mode 100644
index 28162396e80e..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/next.config.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { withSentryConfig } from '@sentry/nextjs/config';
-import type { NextConfig } from 'next';
-
-const nextConfig: NextConfig = {};
-
-export default withSentryConfig(nextConfig, {
- silent: true,
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/package.json
deleted file mode 100644
index 0a5cbe178693..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/package.json
+++ /dev/null
@@ -1,51 +0,0 @@
-{
- "name": "nextjs-16-cf-workers",
- "version": "0.1.0",
- "private": true,
- "scripts": {
- "dev": "next dev",
- "cf:build": "opennextjs-cloudflare build",
- "cf:preview": "opennextjs-cloudflare preview --var \"E2E_TEST_DSN:$E2E_TEST_DSN\"",
- "build": "next build",
- "clean": "npx rimraf node_modules pnpm-lock.yaml .tmp_dev_server_logs",
- "start": "pnpm cf:preview",
- "lint": "eslint",
- "test:prod": "TEST_ENV=production playwright test",
- "test:build": "pnpm install && pnpm cf:build",
- "test:build-canary": "pnpm install && pnpm add next@canary && pnpm cf:build",
- "test:build-latest": "pnpm install && pnpm add next@latest && pnpm cf:build",
- "test:assert": "pnpm test:prod"
- },
- "dependencies": {
- "@opentelemetry/api": "^1.9.1",
- "@opennextjs/cloudflare": "^1.14.9",
- "@sentry/nextjs": "file:../../packed/sentry-nextjs-packed.tgz",
- "@sentry/core": "file:../../packed/sentry-core-packed.tgz",
- "next": "16.2.11",
- "react": "19.1.0",
- "react-dom": "19.1.0"
- },
- "devDependencies": {
- "@playwright/test": "~1.63.0",
- "@sentry-internal/test-utils": "link:../../../test-utils",
- "@types/node": "^20",
- "@types/react": "^19",
- "@types/react-dom": "^19",
- "eslint": "^9",
- "eslint-config-next": "^16",
- "typescript": "^5",
- "wrangler": "^4.61.0"
- },
- "volta": {
- "node": "24.15.0",
- "extends": "../../package.json"
- },
- "sentryTest": {
- "variants": [
- {
- "build-command": "pnpm test:build-latest",
- "label": "nextjs-16-cf-workers (latest)"
- }
- ]
- }
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/playwright.config.mjs
deleted file mode 100644
index 0f15639161dd..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/playwright.config.mjs
+++ /dev/null
@@ -1,21 +0,0 @@
-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 === 'production') {
- return 'pnpm cf:preview --port 3030';
- }
-
- throw new Error(`Unknown test env: ${testEnv}`);
-};
-
-const config = getPlaywrightConfig({
- startCommand: getStartCommand(),
- port: 3030,
-});
-
-export default config;
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/file.svg b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/file.svg
deleted file mode 100644
index 004145cddf3f..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/file.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/globe.svg b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/globe.svg
deleted file mode 100644
index 567f17b0d7c7..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/globe.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/next.svg b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/next.svg
deleted file mode 100644
index 5174b28c565c..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/next.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/vercel.svg b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/vercel.svg
deleted file mode 100644
index 77053960334e..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/vercel.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/window.svg b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/window.svg
deleted file mode 100644
index b2b2a44f6ebc..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/window.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/sentry.edge.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/sentry.edge.config.ts
deleted file mode 100644
index eceebb29d45d..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/sentry.edge.config.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import * as Sentry from '@sentry/nextjs';
-
-// Only hands over the build release: `withSentry` from `@sentry/nextjs/cloudflare` in worker.ts creates the client and
-// holds the options.
-Sentry.init({
- dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/sentry.server.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/sentry.server.config.ts
deleted file mode 100644
index 1009ee9e1f39..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/sentry.server.config.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import * as Sentry from '@sentry/nextjs';
-
-// Only hands over the build release and adds the global Next.js event processors and the turbopack tag: `withSentry`
-// from `@sentry/nextjs/cloudflare` in worker.ts creates the client and holds the options.
-Sentry.init({
- dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/start-event-proxy.mjs
deleted file mode 100644
index efb664370443..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/start-event-proxy.mjs
+++ /dev/null
@@ -1,14 +0,0 @@
-import * as fs from 'fs';
-import * as path from 'path';
-import { startEventProxyServer } from '@sentry-internal/test-utils';
-
-const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json')));
-
-startEventProxyServer({
- port: 3031,
- proxyServerName: 'nextjs-16-cf-workers',
- envelopeDumpPath: path.join(
- process.cwd(),
- `event-dumps/next-16-v${packageJson.dependencies.next}-${process.env.TEST_ENV}.dump`,
- ),
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/async-params.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/async-params.test.ts
deleted file mode 100644
index e8160d12aded..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/async-params.test.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { expect, test } from '@playwright/test';
-import fs from 'fs';
-import { isDevMode } from './isDevMode';
-
-test('should not print warning for async params', async ({ page }) => {
- test.skip(!isDevMode, '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 16 test app')).toBeVisible();
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/isDevMode.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/isDevMode.ts
deleted file mode 100644
index d2be94232110..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/isDevMode.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const isDevMode = !!process.env.TEST_ENV && process.env.TEST_ENV.includes('development');
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/isr-routes.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/isr-routes.test.ts
deleted file mode 100644
index fe61a6263913..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/isr-routes.test.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
-
-test('should remove sentry-trace and baggage meta tags on ISR dynamic route page load', async ({ page }) => {
- // Navigate to ISR page
- await page.goto('/isr-test/laptop');
-
- // Wait for page to be fully loaded
- await expect(page.locator('#isr-product-id')).toHaveText('laptop');
-
- // Check that sentry-trace and baggage meta tags are removed for ISR pages
- await expect(page.locator('meta[name="sentry-trace"]')).toHaveCount(0);
- await expect(page.locator('meta[name="baggage"]')).toHaveCount(0);
-});
-
-test('should remove sentry-trace and baggage meta tags on ISR static route', async ({ page }) => {
- // Navigate to ISR static page
- await page.goto('/isr-test/static');
-
- // Wait for page to be fully loaded
- await expect(page.locator('#isr-static-marker')).toHaveText('static-isr');
-
- // Check that sentry-trace and baggage meta tags are removed for ISR pages
- await expect(page.locator('meta[name="sentry-trace"]')).toHaveCount(0);
- await expect(page.locator('meta[name="baggage"]')).toHaveCount(0);
-});
-
-test('should remove meta tags for different ISR dynamic route values', async ({ page }) => {
- // Test with 'phone' (one of the pre-generated static params)
- await page.goto('/isr-test/phone');
- await expect(page.locator('#isr-product-id')).toHaveText('phone');
-
- await expect(page.locator('meta[name="sentry-trace"]')).toHaveCount(0);
- await expect(page.locator('meta[name="baggage"]')).toHaveCount(0);
-
- // Test with 'tablet'
- await page.goto('/isr-test/tablet');
- await expect(page.locator('#isr-product-id')).toHaveText('tablet');
-
- await expect(page.locator('meta[name="sentry-trace"]')).toHaveCount(0);
- await expect(page.locator('meta[name="baggage"]')).toHaveCount(0);
-});
-
-test('should create unique traces for ISR pages on each visit', async ({ page }) => {
- const traceIds: string[] = [];
-
- // Load the same ISR page 5 times to ensure cached HTML meta tags are consistently removed
- for (let i = 0; i < 5; i++) {
- const spanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => {
- return span.name === '/isr-test/:product' && getSpanOp(span) === 'pageload' && span.is_segment;
- });
-
- if (i === 0) {
- await page.goto('/isr-test/laptop');
- } else {
- await page.reload();
- }
-
- const span = await spanPromise;
- const traceId = span.trace_id;
-
- expect(traceId).toBeDefined();
- expect(traceId).toMatch(/[a-f0-9]{32}/);
- traceIds.push(traceId!);
- }
-
- // Verify all 5 page loads have unique trace IDs (no reuse of cached/stale meta tags)
- const uniqueTraceIds = new Set(traceIds);
- expect(uniqueTraceIds.size).toBe(5);
-});
-
-test('ISR route should be identified correctly in the route manifest', async ({ page }) => {
- const spanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => {
- return span.name === '/isr-test/:product' && getSpanOp(span) === 'pageload' && span.is_segment;
- });
-
- await page.goto('/isr-test/laptop');
- const span = await spanPromise;
-
- // Verify the span is properly parameterized
- expect(span.name).toBe('/isr-test/:product');
- expect(span.attributes['sentry.segment.name.source']?.value).toBe('route');
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/metrics.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/metrics.test.ts
deleted file mode 100644
index 1ccbe00953de..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/metrics.test.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForMetric } from '@sentry-internal/test-utils';
-
-test('Should emit metrics from server and client', async ({ request, page }) => {
- const clientCountPromise = waitForMetric('nextjs-16-cf-workers', async metric => {
- return metric.name === 'test.page.count';
- });
-
- const clientDistributionPromise = waitForMetric('nextjs-16-cf-workers', async metric => {
- return metric.name === 'test.page.distribution';
- });
-
- const clientGaugePromise = waitForMetric('nextjs-16-cf-workers', async metric => {
- return metric.name === 'test.page.gauge';
- });
-
- const serverCountPromise = waitForMetric('nextjs-16-cf-workers', async metric => {
- return metric.name === 'test.route.handler.count';
- });
-
- const serverDistributionPromise = waitForMetric('nextjs-16-cf-workers', async metric => {
- return metric.name === 'test.route.handler.distribution';
- });
-
- const serverGaugePromise = waitForMetric('nextjs-16-cf-workers', async metric => {
- return metric.name === 'test.route.handler.gauge';
- });
-
- await page.goto('/metrics');
- await page.getByText('Emit').click();
- const clientCount = await clientCountPromise;
- const clientDistribution = await clientDistributionPromise;
- const clientGauge = await clientGaugePromise;
- const serverCount = await serverCountPromise;
- const serverDistribution = await serverDistributionPromise;
- const serverGauge = await serverGaugePromise;
-
- expect(clientCount).toMatchObject({
- timestamp: expect.any(Number),
- trace_id: expect.any(String),
- span_id: expect.any(String),
- name: 'test.page.count',
- type: 'counter',
- value: 1,
- attributes: {
- page: { value: '/metrics', type: 'string' },
- 'random.attribute': { value: 'Apples', type: 'string' },
- 'sentry.environment': { value: 'qa', type: 'string' },
- 'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
- 'sentry.sdk.version': { value: expect.any(String), type: 'string' },
- },
- });
-
- expect(clientDistribution).toMatchObject({
- timestamp: expect.any(Number),
- trace_id: expect.any(String),
- span_id: expect.any(String),
- name: 'test.page.distribution',
- type: 'distribution',
- value: 100,
- attributes: {
- page: { value: '/metrics', type: 'string' },
- 'random.attribute': { value: 'Manzanas', type: 'string' },
- 'sentry.environment': { value: 'qa', type: 'string' },
- 'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
- 'sentry.sdk.version': { value: expect.any(String), type: 'string' },
- },
- });
-
- expect(clientGauge).toMatchObject({
- timestamp: expect.any(Number),
- trace_id: expect.any(String),
- span_id: expect.any(String),
- name: 'test.page.gauge',
- type: 'gauge',
- value: 200,
- attributes: {
- page: { value: '/metrics', type: 'string' },
- 'random.attribute': { value: 'Mele', type: 'string' },
- 'sentry.environment': { value: 'qa', type: 'string' },
- 'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
- 'sentry.sdk.version': { value: expect.any(String), type: 'string' },
- },
- });
-
- expect(serverCount).toMatchObject({
- timestamp: expect.any(Number),
- trace_id: expect.any(String),
- name: 'test.route.handler.count',
- type: 'counter',
- value: 1,
- attributes: {
- 'random.attribute': { value: 'Potatoes', type: 'string' },
- endpoint: { value: '/metrics/route-handler', type: 'string' },
- 'sentry.environment': { value: 'qa', type: 'string' },
- 'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
- 'sentry.sdk.version': { value: expect.any(String), type: 'string' },
- },
- });
-
- expect(serverDistribution).toMatchObject({
- timestamp: expect.any(Number),
- trace_id: expect.any(String),
- name: 'test.route.handler.distribution',
- type: 'distribution',
- value: 100,
- attributes: {
- 'random.attribute': { value: 'Patatas', type: 'string' },
- endpoint: { value: '/metrics/route-handler', type: 'string' },
- 'sentry.environment': { value: 'qa', type: 'string' },
- 'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
- 'sentry.sdk.version': { value: expect.any(String), type: 'string' },
- },
- });
-
- expect(serverGauge).toMatchObject({
- timestamp: expect.any(Number),
- trace_id: expect.any(String),
- name: 'test.route.handler.gauge',
- type: 'gauge',
- value: 200,
- attributes: {
- 'random.attribute': { value: 'Patate', type: 'string' },
- endpoint: { value: '/metrics/route-handler', type: 'string' },
- 'sentry.environment': { value: 'qa', type: 'string' },
- 'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
- 'sentry.sdk.version': { value: expect.any(String), type: 'string' },
- },
- });
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/middleware.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/middleware.test.ts
deleted file mode 100644
index 213c40a40a62..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/middleware.test.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { expect, test } from '@playwright/test';
-import {
- collectStreamedSpans,
- collectStreamedSpansUntilSegment,
- getSpanOp,
- waitForStreamedSpan,
-} from '@sentry-internal/test-utils';
-import { isDevMode } from './isDevMode';
-
-// `withSentry` from `@sentry/nextjs/cloudflare` wraps the Worker entry, so middleware runs inside the request's
-// `http.server` segment and its span is a child of it rather than a segment of its own.
-test('Should create a span for middleware inside the request segment', async ({ request }) => {
- const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', 'GET /api/endpoint-behind-middleware');
-
- const response = await request.get('/api/endpoint-behind-middleware');
- expect(await response.json()).toStrictEqual({ name: 'John Doe' });
-
- const spans = await spansPromise;
- const segmentSpan = spans.find(span => span.is_segment)!;
- const middlewareSpan = spans.find(span => span.name === 'middleware GET')!;
-
- expect(segmentSpan.attributes['url.path']?.value).toBe('/api/endpoint-behind-middleware');
- expect(segmentSpan.attributes['sentry.segment.name.source']?.value).toBe('route');
- expect(segmentSpan.attributes['isolation_scope.is_default']).toEqual({ value: false, type: 'boolean' });
- expect(middlewareSpan.is_segment).toBe(false);
- expect(spans.some(span => span.span_id === middlewareSpan.parent_span_id)).toBe(true);
- expect(middlewareSpan.status).toBe('ok');
- expect(getSpanOp(middlewareSpan)).toBe('middleware');
-});
-
-test('Faulty middlewares', async ({ request }) => {
- test.skip(isDevMode, 'Throwing crashes the dev server atm'); // https://github.com/vercel/next.js/issues/85261
- const middlewareSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => {
- return span.name === 'middleware GET' && span.status === 'error';
- });
-
- request.get('/api/endpoint-behind-middleware', { headers: { 'x-should-throw': '1' } }).catch(() => {
- // Noop
- });
-
- await test.step('should record spans', async () => {
- const middlewareSpan = await middlewareSpanPromise;
- expect(middlewareSpan.is_segment).toBe(false);
- expect(getSpanOp(middlewareSpan)).toBe('middleware');
- });
-});
-
-test('Should trace outgoing fetch requests inside middleware', async ({ request }) => {
- test.skip(isDevMode, 'The fetch requests ends up in a separate tx in dev atm');
-
- // `http.client` span names are low cardinality under span streaming, hence `GET localhost` rather
- // than the full URL.
- const spansPromise = collectStreamedSpans('nextjs-16-cf-workers', spans =>
- spans.some(span => getSpanOp(span) === 'http.client' && span.name === 'GET localhost'),
- );
-
- request.get('/api/endpoint-behind-middleware', { headers: { 'x-should-make-request': '1' } }).catch(() => {
- // Noop
- });
-
- const spans = await spansPromise;
- const fetchSpan = spans.find(span => getSpanOp(span) === 'http.client' && span.name === 'GET localhost')!;
-
- expect(fetchSpan.status).toBe('ok');
- expect(fetchSpan.attributes).toMatchObject({
- 'http.request.method': { value: 'GET', type: 'string' },
- 'http.response.status_code': { value: 200, type: 'integer' },
- 'url.full': { value: 'http://localhost:3030/', type: 'string' },
- });
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/nested-rsc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/nested-rsc-error.test.ts
deleted file mode 100644
index 03f7d224e466..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/nested-rsc-error.test.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils';
-
-test('Should capture errors from nested server components when `Sentry.captureRequestError` is added to the `onRequestError` hook', async ({
- page,
-}) => {
- const errorEventPromise = waitForError('nextjs-16-cf-workers', errorEvent => {
- return !!errorEvent?.exception?.values?.some(value => value.value === 'I am technically uncatchable');
- });
-
- // Matched on the error's own trace so a span from an earlier spec cannot satisfy the correlation.
- const serverSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', async span => {
- return (
- span.name === 'GET /nested-rsc-error/[param]' &&
- span.is_segment &&
- (await errorEventPromise).contexts?.trace?.trace_id === span.trace_id
- );
- });
-
- await page.goto(`/nested-rsc-error/123`);
- const errorEvent = await errorEventPromise;
- const serverSpan = await serverSpanPromise;
-
- // error event is part of the same trace as the server span
- expect(errorEvent.contexts?.trace?.trace_id).toBe(serverSpan.trace_id);
-
- expect(errorEvent.request).toMatchObject({
- headers: expect.any(Object),
- method: 'GET',
- });
-
- expect(errorEvent.contexts?.nextjs).toEqual({
- route_type: 'render',
- router_kind: 'App Router',
- router_path: '/nested-rsc-error/[param]',
- request_path: '/nested-rsc-error/123',
- });
-
- expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual({
- handled: false,
- type: 'auto.function.nextjs.on_request_error',
- });
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/pageload-tracing.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/pageload-tracing.test.ts
deleted file mode 100644
index d6165244752e..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/pageload-tracing.test.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
-
-test('App router spans should be attached to the pageload request span', async ({ page }) => {
- const serverSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => {
- return span.name === 'GET /pageload-tracing' && span.is_segment;
- });
-
- const pageloadSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => {
- return span.name === '/pageload-tracing' && getSpanOp(span) === 'pageload' && span.is_segment;
- });
-
- await page.goto(`/pageload-tracing`);
-
- const [serverSpan, pageloadSpan] = await Promise.all([serverSpanPromise, pageloadSpanPromise]);
-
- expect(pageloadSpan.trace_id).toBeTruthy();
- expect(serverSpan.trace_id).toBe(pageloadSpan.trace_id);
-});
-
-test('extracts HTTP request headers as span attributes', async ({ baseURL }) => {
- const serverSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => {
- const requestId = span.attributes['http.request.header.x-request-id'];
- return (
- span.name === 'GET /pageload-tracing' &&
- span.is_segment &&
- requestId?.type === 'array' &&
- requestId.value[0] === 'nextjs-789'
- );
- });
-
- await fetch(`${baseURL}/pageload-tracing`, {
- headers: {
- 'User-Agent': 'Custom-NextJS-Agent/15.0',
- 'Content-Type': 'text/html',
- 'X-NextJS-Test': 'nextjs-header-value',
- Accept: 'text/html, application/xhtml+xml',
- 'X-Framework': 'Next.js',
- 'X-Request-ID': 'nextjs-789',
- },
- });
-
- const serverSpan = await serverSpanPromise;
-
- expect(serverSpan.attributes).toMatchObject({
- 'http.request.header.user-agent': { value: ['Custom-NextJS-Agent/15.0'], type: 'array' },
- 'http.request.header.content-type': { value: ['text/html'], type: 'array' },
- 'http.request.header.x-nextjs-test': { value: ['nextjs-header-value'], type: 'array' },
- 'http.request.header.accept': { value: ['text/html, application/xhtml+xml'], type: 'array' },
- 'http.request.header.x-framework': { value: ['Next.js'], type: 'array' },
- 'http.request.header.x-request-id': { value: ['nextjs-789'], type: 'array' },
- });
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/parameterized-routes.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/parameterized-routes.test.ts
deleted file mode 100644
index 777130edc5b9..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/parameterized-routes.test.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
-
-test('should create a parameterized pageload span when the `app` directory is used', async ({ page }) => {
- const spanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => {
- return span.name === '/parameterized/:one' && getSpanOp(span) === 'pageload' && span.is_segment;
- });
-
- await page.goto(`/parameterized/cappuccino`);
-
- const span = await spanPromise;
-
- expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/));
- expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/));
- expect(span.attributes).toMatchObject({
- 'sentry.op': { value: 'pageload', type: 'string' },
- 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' },
- 'sentry.segment.name.source': { value: 'route', type: 'string' },
- 'sentry.environment': { value: 'qa', type: 'string' },
- 'react.version': { value: expect.any(String), type: 'string' },
- });
-});
-
-test('should create a span named after the static route when the `app` directory is used', async ({ page }) => {
- const spanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => {
- return span.name === '/parameterized/static' && getSpanOp(span) === 'pageload' && span.is_segment;
- });
-
- await page.goto(`/parameterized/static`);
-
- const span = await spanPromise;
-
- expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/));
- expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/));
- expect(span.attributes).toMatchObject({
- 'sentry.op': { value: 'pageload', type: 'string' },
- 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' },
- 'sentry.segment.name.source': { value: 'route', type: 'string' },
- 'sentry.environment': { value: 'qa', type: 'string' },
- 'react.version': { value: expect.any(String), type: 'string' },
- 'url.template': { value: '/parameterized/static', type: 'string' },
- });
-});
-
-test('should create a partially parameterized pageload span when the `app` directory is used', async ({ page }) => {
- const spanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => {
- return span.name === '/parameterized/:one/beep' && getSpanOp(span) === 'pageload' && span.is_segment;
- });
-
- await page.goto(`/parameterized/cappuccino/beep`);
-
- const span = await spanPromise;
-
- expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/));
- expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/));
- expect(span.attributes).toMatchObject({
- 'sentry.op': { value: 'pageload', type: 'string' },
- 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' },
- 'sentry.segment.name.source': { value: 'route', type: 'string' },
- 'sentry.environment': { value: 'qa', type: 'string' },
- 'react.version': { value: expect.any(String), type: 'string' },
- });
-});
-
-test('should create a nested parameterized pageload span when the `app` directory is used', async ({ page }) => {
- const spanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => {
- return span.name === '/parameterized/:one/beep/:two' && getSpanOp(span) === 'pageload' && span.is_segment;
- });
-
- await page.goto(`/parameterized/cappuccino/beep/espresso`);
-
- const span = await spanPromise;
-
- expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/));
- expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/));
- expect(span.attributes).toMatchObject({
- 'sentry.op': { value: 'pageload', type: 'string' },
- 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' },
- 'sentry.segment.name.source': { value: 'route', type: 'string' },
- 'sentry.environment': { value: 'qa', type: 'string' },
- 'react.version': { value: expect.any(String), type: 'string' },
- });
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/prefetch-spans.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/prefetch-spans.test.ts
deleted file mode 100644
index 53cebde1ff21..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/prefetch-spans.test.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils';
-import { isDevMode } from './isDevMode';
-
-test('Prefetch client spans should have a http.request.prefetch attribute', async ({ page }) => {
- test.skip(isDevMode, "Prefetch requests don't have the prefetch header in dev mode");
-
- // The prefetch span is a child of the pageload segment span, which ends last.
- const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', '/prefetching');
-
- await page.goto(`/prefetching`);
-
- // Make it more likely that nextjs prefetches
- await page.hover('#prefetch-link');
-
- const spans = await spansPromise;
-
- expect(spans).toContainEqual(
- expect.objectContaining({
- attributes: expect.objectContaining({
- 'sentry.op': { value: 'http.client', type: 'string' },
- 'http.request.prefetch': { value: true, type: 'boolean' },
- }),
- }),
- );
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/route-handler.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/route-handler.test.ts
deleted file mode 100644
index f8a8420f3694..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/route-handler.test.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import test, { expect } from '@playwright/test';
-import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
-
-test('Should create a span for node route handlers', async ({ request }) => {
- const routehandlerSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => {
- return span.name === 'GET /route-handler/[xoxo]/node' && span.is_segment;
- });
-
- const response = await request.get('/route-handler/123/node', { headers: { 'x-charly': 'gomez' } });
- expect(await response.json()).toStrictEqual({ message: 'Hello Node Route Handler' });
-
- const routehandlerSpan = await routehandlerSpanPromise;
-
- expect(routehandlerSpan.status).toBe('ok');
- expect(getSpanOp(routehandlerSpan)).toBe('http.server');
- expect(routehandlerSpan.attributes['http.request.header.x-charly']?.value).toEqual(['gomez']);
-});
-
-test('Should create a span for edge route handlers', async ({ request }) => {
- test.skip(true, 'OpenNext does not support the Edge runtime');
- const routehandlerSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => {
- return span.name === 'GET /route-handler/[xoxo]/edge' && span.is_segment;
- });
-
- const response = await request.get('/route-handler/123/edge', { headers: { 'x-charly': 'gomez' } });
- expect(await response.json()).toStrictEqual({ message: 'Hello Edge Route Handler' });
-
- const routehandlerSpan = await routehandlerSpanPromise;
-
- expect(routehandlerSpan.status).toBe('ok');
- expect(getSpanOp(routehandlerSpan)).toBe('http.server');
- expect(routehandlerSpan.attributes['http.request.header.x-charly']?.value).toEqual(['gomez']);
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-action-redirect.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-action-redirect.test.ts
deleted file mode 100644
index 9d06ee350aa4..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-action-redirect.test.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils';
-
-test('Should handle server action redirect without capturing errors', async ({ page }) => {
- // Wait for the initial pageload span
- const pageLoadSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => {
- return span.name === '/redirect/origin' && span.is_segment;
- });
-
- // Navigate to the origin page
- await page.goto('/redirect/origin');
-
- const pageLoadSpan = await pageLoadSpanPromise;
- expect(pageLoadSpan).toBeDefined();
-
- // Wait for the redirect span
- const redirectSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => {
- return span.name === 'GET /redirect/destination' && span.is_segment;
- });
-
- // No error should be captured
- const redirectErrorPromise = waitForError('nextjs-16-cf-workers', async errorEvent => {
- return !!errorEvent;
- });
-
- // Click the redirect button
- await page.click('button[type="submit"]');
-
- await redirectSpanPromise;
-
- // Verify we got redirected to the destination page
- await expect(page).toHaveURL('/redirect/destination');
-
- // Wait for potential errors with a 2 second timeout
- const errorTimeout = new Promise((_, reject) =>
- setTimeout(() => reject(new Error('No error captured (timeout)')), 2000),
- );
-
- // We expect this to timeout since no error should be captured during the redirect
- try {
- await Promise.race([redirectErrorPromise, errorTimeout]);
- throw new Error('Expected no error to be captured, but an error was found');
- } catch (e) {
- // If we get a timeout error (as expected), no error was captured
- expect((e as Error).message).toBe('No error captured (timeout)');
- }
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts
deleted file mode 100644
index 344a62127543..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils';
-
-// Next.js emits these spans itself. The SDK attaches no op, description or function name to
-// them, so asserting `undefined` pins that they stay untouched.
-const nextjsSpan = { op: undefined, description: undefined, codeFunctionName: undefined };
-
-// `withSentry` from `@sentry/nextjs/cloudflare` wraps the Worker entry, so the segment is its `http.server`
-// span, named after the route Next.js resolved.
-test('Sends a span for a request to app router with URL', async ({ page }) => {
- const spansPromise = collectStreamedSpansUntilSegment(
- 'nextjs-16-cf-workers',
- span =>
- span.name === 'GET /parameterized/[one]/beep/[two]' &&
- span.attributes['url.path']?.value === '/parameterized/1337/beep/42',
- );
-
- await page.goto('/parameterized/1337/beep/42');
-
- const spans = await spansPromise;
- const segmentSpan = spans.find(
- span =>
- span.name === 'GET /parameterized/[one]/beep/[two]' &&
- span.is_segment &&
- span.attributes['url.path']?.value === '/parameterized/1337/beep/42',
- )!;
-
- expect(segmentSpan.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/));
- expect(segmentSpan.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/));
- expect(segmentSpan.status).toBe('ok');
- expect(segmentSpan.attributes).toMatchObject({
- 'sentry.op': { value: 'http.server', type: 'string' },
- 'sentry.origin': { value: 'auto.http.cloudflare', type: 'string' },
- 'sentry.sample_rate': { value: 1, type: 'integer' },
- 'sentry.segment.name.source': { value: 'route', type: 'string' },
- 'http.request.method': { value: 'GET', type: 'string' },
- 'http.response.status_code': { value: 200, type: 'integer' },
- 'http.route': { value: '/parameterized/[one]/beep/[two]', type: 'string' },
- 'url.path': { value: '/parameterized/1337/beep/42', type: 'string' },
- 'next.route': { value: '/parameterized/[one]/beep/[two]', type: 'string' },
- });
-
- // No child span should share the segment span's name
- expect(spans.filter(span => !span.is_segment && span.name === segmentSpan.name)).toHaveLength(0);
- expect(spans.filter(span => getSpanOp(span) === 'http.server')).toEqual([segmentSpan]);
-});
-
-test('Will create spans for every server component and metadata generation functions when visiting a page', async ({
- page,
-}) => {
- const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', 'GET /nested-layout');
-
- await page.goto('/nested-layout');
-
- const fullSpans = await spansPromise;
- const spans = fullSpans.map(span => ({
- name: span.name,
- op: getSpanOp(span),
- description: span.attributes['sentry.description']?.value,
- codeFunctionName: span.attributes['code.function.name']?.value,
- }));
-
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout' });
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' });
- // Server component spans: the name is the low-cardinality `code.function.name`, and the
- // segment each one resolved for is on the description.
- expect(spans).toContainEqual({
- name: 'Layout',
- op: 'function',
- description: 'resolve root layout server component',
- codeFunctionName: 'Layout',
- });
- expect(spans).toContainEqual({
- name: 'Layout',
- op: 'function',
- description: 'resolve layout server component "(nested-layout)"',
- codeFunctionName: 'Layout',
- });
- expect(spans).toContainEqual({
- name: 'Layout',
- op: 'function',
- description: 'resolve layout server component "nested-layout"',
- codeFunctionName: 'Layout',
- });
- expect(spans).toContainEqual({
- name: 'Page',
- op: 'function',
- description: 'resolve page server component "/nested-layout"',
- codeFunctionName: 'Page',
- });
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'generateMetadata /(nested-layout)/nested-layout/page' });
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' });
-});
-
-test('Will create spans for every server component and metadata generation functions when visiting a dynamic page', async ({
- page,
-}) => {
- const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', 'GET /nested-layout/[dynamic]');
-
- await page.goto('/nested-layout/123');
-
- const fullSpans = await spansPromise;
- const spans = fullSpans.map(span => ({
- name: span.name,
- op: getSpanOp(span),
- description: span.attributes['sentry.description']?.value,
- codeFunctionName: span.attributes['code.function.name']?.value,
- }));
-
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'resolve page components' });
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout/[dynamic]' });
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' });
- // Server component spans: the name is the low-cardinality `code.function.name`, and the
- // segment each one resolved for is on the description.
- expect(spans).toContainEqual({
- name: 'Layout',
- op: 'function',
- description: 'resolve root layout server component',
- codeFunctionName: 'Layout',
- });
- expect(spans).toContainEqual({
- name: 'Layout',
- op: 'function',
- description: 'resolve layout server component "(nested-layout)"',
- codeFunctionName: 'Layout',
- });
- expect(spans).toContainEqual({
- name: 'Layout',
- op: 'function',
- description: 'resolve layout server component "nested-layout"',
- codeFunctionName: 'Layout',
- });
- expect(spans).toContainEqual({
- name: 'Layout',
- op: 'function',
- description: 'resolve layout server component "[dynamic]"',
- codeFunctionName: 'Layout',
- });
- expect(spans).toContainEqual({
- name: 'Page',
- op: 'function',
- description: 'resolve page server component "/nested-layout/[dynamic]"',
- codeFunctionName: 'Page',
- });
- expect(spans).toContainEqual({
- ...nextjsSpan,
- name: 'generateMetadata /(nested-layout)/nested-layout/[dynamic]/page',
- });
- expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' });
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/streaming-rsc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/streaming-rsc-error.test.ts
deleted file mode 100644
index 1b991f1a9fe4..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/streaming-rsc-error.test.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils';
-
-test('Should capture errors for crashing streaming promises in server components when `Sentry.captureRequestError` is added to the `onRequestError` hook', async ({
- page,
-}) => {
- const errorEventPromise = waitForError('nextjs-16-cf-workers', errorEvent => {
- return !!errorEvent?.exception?.values?.some(value => value.value === 'I am a data streaming error');
- });
-
- // Matched on the error's own trace so a span from an earlier spec cannot satisfy the correlation.
- const serverSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', async span => {
- return (
- span.name === 'GET /streaming-rsc-error/[param]' &&
- span.is_segment &&
- (await errorEventPromise).contexts?.trace?.trace_id === span.trace_id
- );
- });
-
- // The streaming RSC error can interrupt the HTTP response, causing the navigation to reject
- // (e.g. net::ERR_ABORTED) even though the error and transaction are still captured.
- await page.goto(`/streaming-rsc-error/123`).catch(() => {});
- const errorEvent = await errorEventPromise;
- const serverSpan = await serverSpanPromise;
-
- // error event is part of the same trace as the server span
- expect(errorEvent.contexts?.trace?.trace_id).toBe(serverSpan.trace_id);
-
- expect(errorEvent.request).toMatchObject({
- headers: expect.any(Object),
- method: 'GET',
- });
-
- expect(errorEvent.contexts?.nextjs).toEqual({
- route_type: 'render',
- router_kind: 'App Router',
- router_path: '/streaming-rsc-error/[param]',
- request_path: '/streaming-rsc-error/123',
- });
-
- expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual({
- handled: false,
- type: 'auto.function.nextjs.on_request_error',
- });
-});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tsconfig.json b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tsconfig.json
deleted file mode 100644
index cc9ed39b5aa2..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tsconfig.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "compilerOptions": {
- "target": "ES2017",
- "lib": ["dom", "dom.iterable", "esnext"],
- "allowJs": true,
- "skipLibCheck": true,
- "strict": true,
- "noEmit": true,
- "esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "jsx": "react-jsx",
- "incremental": true,
- "plugins": [
- {
- "name": "next"
- }
- ],
- "paths": {
- "@/*": ["./*"]
- }
- },
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts", "**/*.mts"],
- "exclude": ["node_modules"]
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/wrangler.jsonc b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/wrangler.jsonc
deleted file mode 100644
index 16b0271383b1..000000000000
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/wrangler.jsonc
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * For more details on how to configure Wrangler, refer to:
- * https://developers.cloudflare.com/workers/wrangler/configuration/
- */
-/**
- * For more details on how to configure Wrangler, refer to:
- * https://developers.cloudflare.com/workers/wrangler/configuration/
- */
-{
- "$schema": "node_modules/wrangler/config-schema.json",
- "name": "next-cf",
- "main": "./worker.ts",
- // From 2026-02-19 on, wrangler uses the native `process` of workerd, whose `cwd()` is `/bundle` during a request.
- // Next.js then misses its router server context and extracts the incoming trace again, see trace-continuation.test.ts.
- "compatibility_date": "2026-02-19",
- "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
- "assets": {
- "binding": "ASSETS",
- "directory": ".open-next/assets",
- },
- "images": {
- // Enable image optimization
- // see https://opennext.js.org/cloudflare/howtos/image
- "binding": "IMAGES",
- },
- "services": [
- {
- // Self-reference service binding, the service name must match the worker name
- // see https://opennext.js.org/cloudflare/caching
- "binding": "WORKER_SELF_REFERENCE",
- "service": "next-cf",
- },
- ],
- "d1_databases": [
- {
- "binding": "DB",
- "database_name": "e2e",
- "database_id": "00000000-0000-0000-0000-000000000000",
- },
- ],
- "observability": {
- "enabled": true,
- },
- /**
- * Smart Placement
- * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
- */
- // "placement": { "mode": "smart" }
- /**
- * Bindings
- * Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
- * databases, object storage, AI inference, real-time communication and more.
- * https://developers.cloudflare.com/workers/runtime-apis/bindings/
- */
- /**
- * Environment Variables
- * https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
- */
- // "vars": { "MY_VARIABLE": "production_value" }
- /**
- * Note: Use secrets to store sensitive data.
- * https://developers.cloudflare.com/workers/configuration/secrets/
- */
- /**
- * Static Assets
- * https://developers.cloudflare.com/workers/static-assets/binding/
- */
- // "assets": { "directory": "./public/", "binding": "ASSETS" }
- /**
- * Service Bindings (communicate between multiple Workers)
- * https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
- */
- // "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
-}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/ai-mock-server.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16/ai-mock-server.mjs
index b97e0cb162dc..50060d37c309 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16/ai-mock-server.mjs
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/ai-mock-server.mjs
@@ -24,11 +24,12 @@ function sendJson(res, status, obj) {
res.end(JSON.stringify(obj));
}
-let serverPromise;
+/** Port of the mock server. The Playwright global setup starts it, so the app reaches it on every runtime. */
+export const MOCK_AI_PORT = 3032;
-/** Lazily starts the shared mock server and resolves to its port. */
-export function getMockAiPort() {
- serverPromise ??= new Promise(resolve => {
+/** Starts the mock server on `MOCK_AI_PORT` and resolves to a function that stops it. */
+export function startMockAiServer() {
+ return new Promise(resolve => {
const server = createServer(async (req, res) => {
const url = req.url || '';
@@ -84,10 +85,8 @@ export function getMockAiPort() {
res.writeHead(404).end();
});
- server.listen(0, () => {
- resolve(server.address().port);
+ server.listen(MOCK_AI_PORT, () => {
+ resolve(() => new Promise(done => server.close(done)));
});
});
-
- return serverPromise;
}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/d1/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/d1/route.ts
similarity index 100%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/d1/route.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/api/d1/route.ts
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/openai/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/openai/route.ts
index 9315f9d079ce..628b7a239697 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/openai/route.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/openai/route.ts
@@ -1,13 +1,12 @@
import { NextResponse } from 'next/server';
import OpenAI from 'openai';
-import { getMockAiPort } from '../../../ai-mock-server.mjs';
+import { MOCK_AI_PORT } from '../../../ai-mock-server.mjs';
export const dynamic = 'force-dynamic';
export async function GET() {
- const port = await getMockAiPort();
const client = new OpenAI({
- baseURL: `http://localhost:${port}/openai`,
+ baseURL: `http://localhost:${MOCK_AI_PORT}/openai`,
apiKey: 'mock-api-key',
});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/otel-context/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/otel-context/route.ts
similarity index 54%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/otel-context/route.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/api/otel-context/route.ts
index 1ca661b60b63..26199762ea55 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/otel-context/route.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/otel-context/route.ts
@@ -1,4 +1,8 @@
-import { context, createContextKey, trace } from '@opentelemetry/api';
+// The app does not install `@opentelemetry/api`: OpenNext resolves the ESM build of an installed copy when it bundles
+// the Node.js middleware for Workers, but its file tracing only copies the CommonJS build. The copy in Next.js uses the
+// same global OpenTelemetry API, but has no types.
+// @ts-expect-error `next/dist/compiled/@opentelemetry/api` ships no type declarations
+import { context, createContextKey, trace } from 'next/dist/compiled/@opentelemetry/api';
import { NextResponse } from 'next/server';
export const dynamic = 'force-dynamic';
@@ -9,7 +13,7 @@ export async function GET() {
const tracer = trace.getTracer('e2e');
context.with(context.active().setValue(E2E_CONTEXT_KEY, 'e2e-value'), () => {
- tracer.startActiveSpan('otel-context-outer', outer => {
+ tracer.startActiveSpan('otel-context-outer', (outer: { end(): void }) => {
// An explicit context, as OpenTelemetry instrumentations pass it.
const inner = tracer.startSpan('otel-context-inner', {}, context.active());
inner.setAttribute('e2e.context.value', String(context.active().getValue(E2E_CONTEXT_KEY)));
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/test-error/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/test-error/route.ts
similarity index 100%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/test-error/route.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/api/test-error/route.ts
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch-external-disallowed/check/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch-external-disallowed/check/route.ts
similarity index 100%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch-external-disallowed/check/route.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch-external-disallowed/check/route.ts
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch-external-disallowed/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch-external-disallowed/route.ts
similarity index 100%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch-external-disallowed/route.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch-external-disallowed/route.ts
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch/check/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch/check/route.ts
similarity index 100%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch/check/route.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch/check/route.ts
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch/route.ts
similarity index 100%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch/route.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch/route.ts
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/utils.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/utils.ts
similarity index 100%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/utils.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/utils.ts
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/global-setup.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16/global-setup.mjs
index dc7ec8736faf..41ec97defa8f 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16/global-setup.mjs
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/global-setup.mjs
@@ -1,6 +1,7 @@
import { execSync } from 'child_process';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
+import { startMockAiServer } from './ai-mock-server.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -11,4 +12,8 @@ export default async function globalSetup() {
cwd: __dirname,
stdio: 'inherit',
});
+
+ // The mock AI server runs here and not in the app, because a Worker can not start a `node:http` server. Playwright
+ // runs the returned function as teardown.
+ return startMockAiServer();
}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/next.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/next.config.ts
index 8f6c5edfbcca..c49f53a2c851 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16/next.config.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/next.config.ts
@@ -5,6 +5,9 @@ import type { NextConfig } from 'next';
process.env.VERCEL = '1';
const nextConfig: NextConfig = {
+ // `pg` requires `pg-cloudflare` on Workers. The file tracing only copies its Node.js build, and OpenNext copies the
+ // whole package (with its `workerd` build) only for packages listed here.
+ serverExternalPackages: ['pg-cloudflare'],
experimental: {
sri: {
algorithm: 'sha256',
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/open-next.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/open-next.config.ts
similarity index 100%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/open-next.config.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/open-next.config.ts
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16/package.json
index 408cd3be5a38..fcc8aa64f9d8 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16/package.json
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/package.json
@@ -20,10 +20,19 @@
"test:build-latest-webpack": "pnpm install && pnpm add next@latest && pnpm build-webpack",
"test:build-canary-webpack": "pnpm install && pnpm add next@canary && pnpm build-webpack",
"test:assert": "pnpm test:prod && pnpm test:dev",
- "test:assert-webpack": "pnpm test:prod && pnpm test:dev-webpack"
+ "test:assert-webpack": "pnpm test:prod && pnpm test:dev-webpack",
+ "test:assert:bun": "RUNTIME=bun pnpm test:prod",
+ "test:assert:deno": "RUNTIME=deno pnpm test:prod",
+ "test:build:cloudflare": "pnpm install && opennextjs-cloudflare build",
+ "test:build-latest:cloudflare": "pnpm install && pnpm add next@latest && opennextjs-cloudflare build",
+ "test:assert:cloudflare": "RUNTIME=cloudflare pnpm test:prod",
+ "start:cloudflare": "opennextjs-cloudflare preview --var \"E2E_TEST_DSN:$E2E_TEST_DSN\""
},
"//": "Pin `ioredis` to 5.10.1: it's the last version before the driver publishes its own native diagnostics channels; orchestrion's config covers `ioredis <5.11.0`.",
"dependencies": {
+ "@opennextjs/cloudflare": "^1.14.9",
+ "@sentry/bun": "file:../../packed/sentry-bun-packed.tgz",
+ "@sentry/deno": "file:../../packed/sentry-deno-packed.tgz",
"@sentry/nextjs": "file:../../packed/sentry-nextjs-packed.tgz",
"@sentry/core": "file:../../packed/sentry-core-packed.tgz",
"@vercel/queue": "^0.1.3",
@@ -47,7 +56,8 @@
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "^16",
- "typescript": "^5"
+ "typescript": "^5",
+ "wrangler": "^4.61.0"
},
"volta": {
"extends": "../../package.json"
@@ -67,6 +77,20 @@
{
"build-command": "pnpm test:build-latest",
"label": "nextjs-16 (latest, turbopack)"
+ },
+ {
+ "assert-command": "pnpm test:assert:bun",
+ "label": "nextjs-16 (bun)"
+ },
+ {
+ "build-command": "pnpm test:build:cloudflare",
+ "assert-command": "pnpm test:assert:cloudflare",
+ "label": "nextjs-16 (cloudflare)"
+ },
+ {
+ "build-command": "pnpm test:build-latest:cloudflare",
+ "assert-command": "pnpm test:assert:cloudflare",
+ "label": "nextjs-16 (cloudflare, latest)"
}
],
"optionalVariants": [
@@ -79,6 +103,11 @@
"build-command": "pnpm test:build-canary-webpack",
"label": "nextjs-16 (canary, webpack)",
"assert-command": "pnpm test:assert-webpack"
+ },
+ {
+ "assert-command": "pnpm test:assert:deno",
+ "deno-version": "v2.9.0",
+ "label": "nextjs-16 (deno)"
}
]
}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16/playwright.config.mjs
index cbac80f5b7e7..7e356d81488b 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16/playwright.config.mjs
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/playwright.config.mjs
@@ -1,10 +1,17 @@
-import { getPlaywrightConfig } from '@sentry-internal/test-utils';
+import { getPlaywrightConfig, getRuntime } from '@sentry-internal/test-utils';
const testEnv = process.env.TEST_ENV;
if (!testEnv) {
throw new Error('No test env defined');
}
+const productionStartCommands = {
+ node: 'pnpm next start -p 3030',
+ bun: 'bun --bun ./node_modules/next/dist/bin/next start -p 3030',
+ deno: 'deno run -A ./node_modules/next/dist/bin/next start -p 3030',
+ cloudflare: 'pnpm start:cloudflare --port 3030',
+};
+
const getStartCommand = () => {
if (testEnv === 'development-webpack') {
return 'pnpm next dev -p 3030 --webpack 2>&1 | tee .tmp_dev_server_logs';
@@ -15,7 +22,7 @@ const getStartCommand = () => {
}
if (testEnv === 'production') {
- return 'pnpm next start -p 3030';
+ return productionStartCommands[getRuntime()];
}
throw new Error(`Unknown test env: ${testEnv}`);
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/sentry.server.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/sentry.server.config.ts
index 4507c799f9f3..960f49a15533 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16/sentry.server.config.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/sentry.server.config.ts
@@ -1,3 +1,5 @@
+import { bunHttpServerIntegration, fetchIntegration as bunFetchIntegration } from '@sentry/bun';
+import { fetchIntegration as denoFetchIntegration } from '@sentry/deno';
import * as Sentry from '@sentry/nextjs';
import { Log } from '@sentry/nextjs';
@@ -6,8 +8,17 @@ Sentry.init({
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
tracesSampleRate: 1.0,
+ tracePropagationTargets: ['http://localhost:3030/propagation/test-outgoing-fetch/check'],
// debug: true,
- integrations: [Sentry.vercelAIIntegration(), Sentry.nodeRuntimeMetricsIntegration({ collectionIntervalMs: 1_000 })],
+ integrations: [
+ Sentry.vercelAIIntegration(),
+ Sentry.nodeRuntimeMetricsIntegration({ collectionIntervalMs: 1_000 }),
+ // Bun does not emit the `node:http` diagnostics channel the Node SDK uses to isolate incoming requests, so each
+ // request would otherwise share one trace. Next.js emits its own server spans, hence `spans: false`.
+ ...(process.versions.bun ? [bunHttpServerIntegration({ spans: false }), bunFetchIntegration()] : []),
+ // The `fetch` of Deno is not undici, so the Node SDK does not see outgoing requests.
+ ...(process.versions.deno ? [denoFetchIntegration()] : []),
+ ],
// Verify Log type is available
beforeSendLog(log: Log) {
return log;
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/cloudflare-runtime.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/cloudflare-runtime.test.ts
similarity index 79%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/cloudflare-runtime.test.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/tests/cloudflare-runtime.test.ts
index d088edd32dcb..bf1c773190a7 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/cloudflare-runtime.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/cloudflare-runtime.test.ts
@@ -1,9 +1,11 @@
import { expect, test } from '@playwright/test';
-import { waitForError } from '@sentry-internal/test-utils';
+import { getRuntime, waitForError } from '@sentry-internal/test-utils';
+
+test.skip(getRuntime() !== 'cloudflare', 'Checks the runtime context of the Cloudflare Worker');
test.describe('Cloudflare Runtime', () => {
test('Should report cloudflare as the runtime in API route error events', async ({ request }) => {
- const errorEventPromise = waitForError('nextjs-16-cf-workers', errorEvent => {
+ const errorEventPromise = waitForError('nextjs-16', errorEvent => {
return !!errorEvent?.exception?.values?.some(value =>
value.value?.includes('This is a test error from an API route'),
);
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/d1-binding.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/d1-binding.test.ts
similarity index 80%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/d1-binding.test.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/tests/d1-binding.test.ts
index f562907e08f5..5639e1ab41a7 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/d1-binding.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/d1-binding.test.ts
@@ -1,9 +1,11 @@
import { expect, test } from '@playwright/test';
-import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils';
+import { collectStreamedSpansUntilSegment, getRuntime, getSpanOp } from '@sentry-internal/test-utils';
+
+test.skip(getRuntime() !== 'cloudflare', 'D1 is a binding of Cloudflare Workers');
// `withSentry` passes an instrumented `env` to the OpenNext handler, which `getCloudflareContext()` returns.
test('Should create a span for a D1 query through the `env` of `getCloudflareContext`', async ({ request }) => {
- const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', 'GET /api/d1');
+ const spansPromise = collectStreamedSpansUntilSegment('nextjs-16', 'GET /api/d1');
const response = await request.get('/api/d1');
expect(await response.json()).toStrictEqual([{ one: 1 }]);
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts
index 968891a085c4..eb564116a132 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts
@@ -1,5 +1,5 @@
import { expect, test } from '@playwright/test';
-import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils';
+import { collectStreamedSpansUntilSegment, getRuntime } from '@sentry-internal/test-utils';
test('Instruments DB calls made during server-side rendering of a page', async ({ page }) => {
// The db spans are children of the segment span, which ends last.
@@ -12,19 +12,22 @@ test('Instruments DB calls made during server-side rendering of a page', async (
const spans = await spansPromise;
// One page render produces spans from both injection paths: pg (externalized → runtime module
- // hook) and ioredis (bundle-safe allowlisted → build-time loader).
- expect(spans).toContainEqual(
- expect.objectContaining({
- name: 'SELECT',
- status: 'ok',
- attributes: expect.objectContaining({
- 'sentry.op': { value: 'db', type: 'string' },
- 'sentry.origin': { value: 'auto.db.postgres', type: 'string' },
- 'db.system.name': { value: 'postgresql', type: 'string' },
- 'db.query.text': { value: 'SELECT ? + ? AS answer', type: 'string' },
+ // hook) and ioredis (bundle-safe allowlisted → build-time loader). Bun and Workers have no runtime module
+ // hook, so pg creates no spans there.
+ if (getRuntime() !== 'bun' && getRuntime() !== 'cloudflare') {
+ expect(spans).toContainEqual(
+ expect.objectContaining({
+ name: 'SELECT',
+ status: 'ok',
+ attributes: expect.objectContaining({
+ 'sentry.op': { value: 'db', type: 'string' },
+ 'sentry.origin': { value: 'auto.db.postgres', type: 'string' },
+ 'db.system.name': { value: 'postgresql', type: 'string' },
+ 'db.query.text': { value: 'SELECT ? + ? AS answer', type: 'string' },
+ }),
}),
- }),
- );
+ );
+ }
expect(spans).toContainEqual(
expect.objectContaining({
name: 'set localhost:6379',
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/metrics.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/metrics.test.ts
index 43edb917d526..3b002afcddd2 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/metrics.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/metrics.test.ts
@@ -1,7 +1,11 @@
import { expect, test } from '@playwright/test';
-import { waitForMetric } from '@sentry-internal/test-utils';
+import { getRuntime, waitForMetric } from '@sentry-internal/test-utils';
test('Should emit metrics from server and client', async ({ request, page }) => {
+ // The client of `@sentry/cloudflare` has no `serverName`, so its metrics have no `server.address`.
+ const serverAddress =
+ getRuntime() === 'cloudflare' ? {} : { 'server.address': { value: expect.any(String), type: 'string' } };
+
const clientCountPromise = waitForMetric('nextjs-16', async metric => {
return metric.name === 'test.page.count';
});
@@ -90,7 +94,7 @@ test('Should emit metrics from server and client', async ({ request, page }) =>
type: 'counter',
value: 1,
attributes: {
- 'server.address': { value: expect.any(String), type: 'string' },
+ ...serverAddress,
'random.attribute': { value: 'Potatoes', type: 'string' },
endpoint: { value: '/metrics/route-handler', type: 'string' },
'sentry.environment': { value: 'qa', type: 'string' },
@@ -106,7 +110,7 @@ test('Should emit metrics from server and client', async ({ request, page }) =>
type: 'distribution',
value: 100,
attributes: {
- 'server.address': { value: expect.any(String), type: 'string' },
+ ...serverAddress,
'random.attribute': { value: 'Patatas', type: 'string' },
endpoint: { value: '/metrics/route-handler', type: 'string' },
'sentry.environment': { value: 'qa', type: 'string' },
@@ -122,7 +126,7 @@ test('Should emit metrics from server and client', async ({ request, page }) =>
type: 'gauge',
value: 200,
attributes: {
- 'server.address': { value: expect.any(String), type: 'string' },
+ ...serverAddress,
'random.attribute': { value: 'Patate', type: 'string' },
endpoint: { value: '/metrics/route-handler', type: 'string' },
'sentry.environment': { value: 'qa', type: 'string' },
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/middleware.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/middleware.test.ts
index f91168e54038..9359c0a96c7c 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/middleware.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/middleware.test.ts
@@ -2,12 +2,15 @@ import { expect, test } from '@playwright/test';
import {
collectStreamedSpans,
collectStreamedSpansUntilSegment,
+ getRuntime,
getSpanOp,
waitForStreamedSpan,
} from '@sentry-internal/test-utils';
import { isDevMode } from './isDevMode';
test('Should create a span for middleware', async ({ request }) => {
+ test.skip(getRuntime() === 'cloudflare', 'On Workers the middleware span is a child of the request span');
+
const spansPromise = collectStreamedSpansUntilSegment('nextjs-16', 'middleware GET');
const routeSpanPromise = waitForStreamedSpan('nextjs-16', span => {
@@ -44,8 +47,32 @@ test('Should create a span for middleware', async ({ request }) => {
expect(routeSpan.attributes['isolation_scope.has_proxy_marker']).toEqual({ value: false, type: 'boolean' });
});
+// `withSentry` from `@sentry/nextjs/cloudflare` wraps the Worker entry, so middleware runs inside the request's
+// `http.server` segment and its span is a child of it rather than a segment of its own.
+test('Should create a span for middleware inside the request segment of the Worker', async ({ request }) => {
+ test.skip(getRuntime() !== 'cloudflare', 'Only on Workers the request span of `withSentry` is the segment');
+
+ const spansPromise = collectStreamedSpansUntilSegment('nextjs-16', 'GET /api/endpoint-behind-middleware');
+
+ const response = await request.get('/api/endpoint-behind-middleware');
+ expect(await response.json()).toStrictEqual({ name: 'John Doe' });
+
+ const spans = await spansPromise;
+ const segmentSpan = spans.find(span => span.is_segment)!;
+ const middlewareSpan = spans.find(span => span.name === 'middleware GET')!;
+
+ expect(segmentSpan.attributes['url.path']?.value).toBe('/api/endpoint-behind-middleware');
+ expect(segmentSpan.attributes['sentry.segment.name.source']?.value).toBe('route');
+ expect(segmentSpan.attributes['isolation_scope.is_default']).toEqual({ value: false, type: 'boolean' });
+ expect(middlewareSpan.is_segment).toBe(false);
+ expect(spans.some(span => span.span_id === middlewareSpan.parent_span_id)).toBe(true);
+ expect(middlewareSpan.status).toBe('ok');
+ expect(getSpanOp(middlewareSpan)).toBe('middleware');
+});
+
test('Faulty middlewares', async ({ request }) => {
test.skip(isDevMode, 'Throwing crashes the dev server atm'); // https://github.com/vercel/next.js/issues/85261
+ test.skip(getRuntime() === 'cloudflare', 'On Workers the middleware span is a child of the request span');
const middlewareSpanPromise = waitForStreamedSpan('nextjs-16', span => {
return span.name === 'middleware GET' && span.is_segment;
});
@@ -65,6 +92,21 @@ test('Faulty middlewares', async ({ request }) => {
// await test.step('should record exceptions', async () => { ... });
});
+test('Faulty middlewares inside the request segment of the Worker', async ({ request }) => {
+ test.skip(getRuntime() !== 'cloudflare', 'Only on Workers the request span of `withSentry` is the segment');
+ const middlewareSpanPromise = waitForStreamedSpan('nextjs-16', span => {
+ return span.name === 'middleware GET' && span.status === 'error';
+ });
+
+ request.get('/api/endpoint-behind-middleware', { headers: { 'x-should-throw': '1' } }).catch(() => {
+ // Noop
+ });
+
+ const middlewareSpan = await middlewareSpanPromise;
+ expect(middlewareSpan.is_segment).toBe(false);
+ expect(getSpanOp(middlewareSpan)).toBe('middleware');
+});
+
test('Should trace outgoing fetch requests inside middleware', async ({ request }) => {
test.skip(isDevMode, 'The fetch requests ends up in a separate tx in dev atm');
@@ -86,14 +128,20 @@ test('Should trace outgoing fetch requests inside middleware', async ({ request
expect(fetchSpan.attributes).toMatchObject({
'http.request.method': { value: 'GET', type: 'string' },
'http.response.status_code': { value: 200, type: 'integer' },
- 'sentry.kind': { value: 'client', type: 'string' },
'sentry.op': { value: 'http.client', type: 'string' },
- 'sentry.origin': { value: 'auto.http.node_fetch', type: 'string' },
'server.address': { value: 'localhost', type: 'string' },
'server.port': { value: 3030, type: 'integer' },
'url.domain': { value: 'localhost', type: 'string' },
'url.full': { value: 'http://localhost:3030/', type: 'string' },
- 'url.path': { value: '/', type: 'string' },
- 'url.scheme': { value: 'http', type: 'string' },
+ // Only Node.js has the undici diagnostics channel. On the other runtimes the fetch integration of the runtime
+ // SDK creates this span.
+ ...(getRuntime() === 'node'
+ ? {
+ 'sentry.kind': { value: 'client', type: 'string' },
+ 'sentry.origin': { value: 'auto.http.node_fetch', type: 'string' },
+ 'url.path': { value: '/', type: 'string' },
+ 'url.scheme': { value: 'http', type: 'string' },
+ }
+ : { 'sentry.origin': { value: 'auto.http.fetch', type: 'string' } }),
});
});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/node-runtime-metrics.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/node-runtime-metrics.test.ts
index 5295101f1e20..7f611cf3daa9 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/node-runtime-metrics.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/node-runtime-metrics.test.ts
@@ -1,5 +1,7 @@
import { expect, test } from '@playwright/test';
-import { waitForMetric } from '@sentry-internal/test-utils';
+import { getRuntime, waitForMetric } from '@sentry-internal/test-utils';
+
+test.skip(getRuntime() === 'cloudflare', 'Node.js runtime metrics do not exist on Workers');
const EXPECTED_ATTRIBUTES = {
'sentry.environment': { value: 'qa', type: 'string' },
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/otel-context.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/otel-context.test.ts
similarity index 95%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/otel-context.test.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/tests/otel-context.test.ts
index f4fee5dbc61e..7ed91bec5393 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/otel-context.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/otel-context.test.ts
@@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test';
import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils';
test('Keeps OpenTelemetry context values and parents', async ({ request }) => {
- const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', 'GET /api/otel-context');
+ const spansPromise = collectStreamedSpansUntilSegment('nextjs-16', 'GET /api/otel-context');
const response = await request.get('/api/otel-context');
expect(await response.json()).toStrictEqual({ ok: true });
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/propagation.test.ts
similarity index 82%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/propagation.test.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/tests/propagation.test.ts
index e82fb6453474..72da87388bfc 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/propagation.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/propagation.test.ts
@@ -5,12 +5,14 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL, request }
// Inbound span, outbound span and the http.client span in between all share one trace, and
// `collectStreamedSpans` evaluates a single trace at a time, so requiring all three together
// keeps them paired.
- const spansPromise = collectStreamedSpans('nextjs-16-bun', spans => {
+ const spansPromise = collectStreamedSpans('nextjs-16', spans => {
return (
spans.some(span => span.name === 'GET /propagation/test-outgoing-fetch' && span.is_segment) &&
spans.some(span => span.name === 'GET /propagation/test-outgoing-fetch/check' && span.is_segment) &&
spans.some(
- span => getSpanOp(span) === 'http.client' && span.attributes['sentry.origin']?.value === 'auto.http.fetch',
+ span =>
+ getSpanOp(span) === 'http.client' &&
+ String(span.attributes['url.full']?.value).endsWith('/propagation/test-outgoing-fetch/check'),
)
);
});
@@ -23,7 +25,9 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL, request }
span => span.name === 'GET /propagation/test-outgoing-fetch/check' && span.is_segment,
)!;
const httpClientSpan = spans.find(
- span => getSpanOp(span) === 'http.client' && span.attributes['sentry.origin']?.value === 'auto.http.fetch',
+ span =>
+ getSpanOp(span) === 'http.client' &&
+ String(span.attributes['url.full']?.value).endsWith('/propagation/test-outgoing-fetch/check'),
);
expect(inboundSpan.trace_id).toStrictEqual(expect.any(String));
@@ -44,11 +48,11 @@ test('Does not propagate outgoing fetch requests not covered by tracePropagation
request,
}) => {
// These two spans are deliberately in different traces, so they are matched by their unique names.
- const inboundSpanPromise = waitForStreamedSpan('nextjs-16-bun', span => {
+ const inboundSpanPromise = waitForStreamedSpan('nextjs-16', span => {
return span.name === 'GET /propagation/test-outgoing-fetch-external-disallowed/check' && span.is_segment;
});
- const outboundSpanPromise = waitForStreamedSpan('nextjs-16-bun', span => {
+ const outboundSpanPromise = waitForStreamedSpan('nextjs-16', span => {
return span.name === 'GET /propagation/test-outgoing-fetch-external-disallowed' && span.is_segment;
});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts
index b271cfd1ac52..1d7b0d59ffe7 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts
@@ -1,5 +1,5 @@
import { expect, test } from '@playwright/test';
-import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils';
+import { collectStreamedSpansUntilSegment, getRuntime, getSpanOp } from '@sentry-internal/test-utils';
import { isTurbopackDevMode } from './isDevMode';
// Next.js emits these spans itself. The SDK attaches no op, description or function name to
@@ -9,11 +9,15 @@ const nextjsSpan = { op: undefined, description: undefined, codeFunctionName: un
test('Sends a span for a request to app router with URL', async ({ page }) => {
test.skip(isTurbopackDevMode, 'Turbopack intermittently returns 404 for nested dynamic routes in dev mode');
+ // On Workers the segment is the request span of `withSentry` from `@sentry/nextjs/cloudflare`, with the attributes
+ // of `@sentry/cloudflare`. On the other runtimes it is the request span of Next.js.
+ const pathAttribute = getRuntime() === 'cloudflare' ? 'url.path' : 'http.target';
+
const spansPromise = collectStreamedSpansUntilSegment(
'nextjs-16',
span =>
span.name === 'GET /parameterized/[one]/beep/[two]' &&
- String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'),
+ String(span.attributes[pathAttribute]?.value).startsWith('/parameterized/1337/beep/42'),
);
await page.goto('/parameterized/1337/beep/42');
@@ -23,7 +27,7 @@ test('Sends a span for a request to app router with URL', async ({ page }) => {
span =>
span.name === 'GET /parameterized/[one]/beep/[two]' &&
span.is_segment &&
- String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'),
+ String(span.attributes[pathAttribute]?.value).startsWith('/parameterized/1337/beep/42'),
)!;
expect(segmentSpan.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/));
@@ -31,20 +35,29 @@ test('Sends a span for a request to app router with URL', async ({ page }) => {
expect(segmentSpan.status).toBe('ok');
expect(segmentSpan.attributes).toMatchObject({
'sentry.op': { value: 'http.server', type: 'string' },
- 'sentry.origin': { value: 'auto', type: 'string' },
'sentry.sample_rate': { value: 1, type: 'integer' },
'sentry.segment.name.source': { value: 'route', type: 'string' },
- 'http.method': { value: 'GET', type: 'string' },
'http.response.status_code': { value: 200, type: 'integer' },
'http.route': { value: '/parameterized/[one]/beep/[two]', type: 'string' },
- 'http.status_code': { value: 200, type: 'integer' },
- 'http.target': { value: '/parameterized/1337/beep/42', type: 'string' },
- 'sentry.kind': { value: 'server', type: 'string' },
'next.route': { value: '/parameterized/[one]/beep/[two]', type: 'string' },
+ ...(getRuntime() === 'cloudflare'
+ ? {
+ 'sentry.origin': { value: 'auto.http.cloudflare', type: 'string' },
+ 'http.request.method': { value: 'GET', type: 'string' },
+ 'url.path': { value: '/parameterized/1337/beep/42', type: 'string' },
+ }
+ : {
+ 'sentry.origin': { value: 'auto', type: 'string' },
+ 'http.method': { value: 'GET', type: 'string' },
+ 'http.status_code': { value: 200, type: 'integer' },
+ 'http.target': { value: '/parameterized/1337/beep/42', type: 'string' },
+ 'sentry.kind': { value: 'server', type: 'string' },
+ }),
});
// No child span should share the segment span's name
expect(spans.filter(span => !span.is_segment && span.name === segmentSpan.name)).toHaveLength(0);
+ expect(spans.filter(span => getSpanOp(span) === 'http.server')).toEqual([segmentSpan]);
});
test('Will create spans for every server component and metadata generation functions when visiting a page', async ({
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/trace-continuation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/trace-continuation.test.ts
similarity index 58%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/trace-continuation.test.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/tests/trace-continuation.test.ts
index 447aa7df0cb6..7971e3975dbb 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/trace-continuation.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/trace-continuation.test.ts
@@ -1,18 +1,21 @@
import { expect, test } from '@playwright/test';
-import { collectStreamedSpans } from '@sentry-internal/test-utils';
+import { collectStreamedSpans, getRuntime } from '@sentry-internal/test-utils';
-test('Continues an incoming trace in the segment of the Worker request', async ({ request }) => {
+// On Cloudflare the segment is the request span of `withSentry`. It is matched by its origin, because Next.js can
+// start its own request span under the upstream span too, which would be a second segment.
+test('Continues an incoming trace in the segment of the request', async ({ request }) => {
const traceId = crypto.randomUUID().replace(/-/g, '');
const parentSpanId = traceId.slice(0, 16);
const spansPromise = collectStreamedSpans(
- 'nextjs-16-cf-workers',
+ 'nextjs-16',
spans =>
spans.some(
span =>
span.trace_id === traceId &&
span.is_segment &&
- span.attributes['sentry.origin']?.value === 'auto.http.cloudflare',
+ span.parent_span_id === parentSpanId &&
+ (getRuntime() !== 'cloudflare' || span.attributes['sentry.origin']?.value === 'auto.http.cloudflare'),
) &&
spans.some(
span => span.trace_id === traceId && span.name === 'render route (app) /parameterized/[one]/beep/[two]',
@@ -28,11 +31,13 @@ test('Continues an incoming trace in the segment of the Worker request', async (
const spans = await spansPromise;
const segmentSpan = spans.find(
- span => span.is_segment && span.attributes['sentry.origin']?.value === 'auto.http.cloudflare',
+ span =>
+ span.is_segment &&
+ span.parent_span_id === parentSpanId &&
+ (getRuntime() !== 'cloudflare' || span.attributes['sentry.origin']?.value === 'auto.http.cloudflare'),
)!;
const renderSpan = spans.find(span => span.name === 'render route (app) /parameterized/[one]/beep/[two]')!;
- expect(segmentSpan.parent_span_id).toBe(parentSpanId);
expect(segmentSpan.name).toBe('GET /parameterized/[one]/beep/[two]');
expect(renderSpan.attributes['sentry.segment.id']?.value).toBe(segmentSpan.span_id);
});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/worker-bundle.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/worker-bundle.test.ts
similarity index 95%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/worker-bundle.test.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/tests/worker-bundle.test.ts
index dbd927e2053b..af16d1add37d 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/worker-bundle.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/worker-bundle.test.ts
@@ -1,9 +1,12 @@
import { expect, test } from '@playwright/test';
+import { getRuntime } from '@sentry-internal/test-utils';
import * as fs from 'fs';
import { createRequire } from 'module';
import * as path from 'path';
import { isDevMode } from './isDevMode';
+test.skip(getRuntime() !== 'cloudflare', 'Checks the worker bundle of OpenNext');
+
/**
* The orchestrion bundler plugins are build-time-only, and their module-scope side effects break
* on Workers (an unawaited `WebAssembly.compile()` crashed every cold start, issue #22794). The
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/worker.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/worker.ts
similarity index 87%
rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/worker.ts
rename to dev-packages/e2e-tests/test-applications/nextjs-16/worker.ts
index 6718e015a32c..d57c23a07c81 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/worker.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/worker.ts
@@ -11,6 +11,7 @@ export default Sentry.withSentry(
dsn: env.E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
tracesSampleRate: 1.0,
+ tracePropagationTargets: ['http://localhost:3030/propagation/test-outgoing-fetch/check'],
integrations: [Sentry.vercelAIIntegration()],
}),
handler,
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/wrangler.jsonc b/dev-packages/e2e-tests/test-applications/nextjs-16/wrangler.jsonc
new file mode 100644
index 000000000000..f6bfe6a4ec38
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16/wrangler.jsonc
@@ -0,0 +1,36 @@
+{
+ "$schema": "node_modules/wrangler/config-schema.json",
+ "name": "nextjs-16",
+ "main": "./worker.ts",
+ // From 2026-02-19 on, wrangler uses the native `process` of workerd, whose `cwd()` is `/bundle` during a request.
+ // Next.js then misses its router server context and extracts the incoming trace again, see trace-continuation.test.ts.
+ "compatibility_date": "2026-02-19",
+ "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
+ "assets": {
+ "binding": "ASSETS",
+ "directory": ".open-next/assets",
+ },
+ "images": {
+ // Enable image optimization
+ // see https://opennext.js.org/cloudflare/howtos/image
+ "binding": "IMAGES",
+ },
+ "services": [
+ {
+ // Self-reference service binding, the service name must match the worker name
+ // see https://opennext.js.org/cloudflare/caching
+ "binding": "WORKER_SELF_REFERENCE",
+ "service": "nextjs-16",
+ },
+ ],
+ "d1_databases": [
+ {
+ "binding": "DB",
+ "database_name": "e2e",
+ "database_id": "00000000-0000-0000-0000-000000000000",
+ },
+ ],
+ "observability": {
+ "enabled": true,
+ },
+}