diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/.gitignore b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/.gitignore
new file mode 100644
index 000000000000..dd146b53d966
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/.gitignore
@@ -0,0 +1,44 @@
+# 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-cacheComponents/.npmrc b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/.npmrc
new file mode 100644
index 000000000000..a3160f4de175
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/.npmrc
@@ -0,0 +1,4 @@
+@sentry:registry=http://127.0.0.1:4873
+@sentry-internal:registry=http://127.0.0.1:4873
+public-hoist-pattern[]=*import-in-the-middle*
+public-hoist-pattern[]=*require-in-the-middle*
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/cache/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/cache/page.tsx
new file mode 100644
index 000000000000..6cf490b75430
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/cache/page.tsx
@@ -0,0 +1,25 @@
+import { Suspense } from 'react';
+import * as Sentry from '@sentry/nextjs';
+
+export default function Page() {
+ return (
+ <>
+
This will be pre-rendered
+
+ >
+ );
+}
+
+async function DynamicContent() {
+ const getTodos = async () => {
+ return Sentry.startSpan({ name: 'getTodos', op: 'get.todos' }, async () => {
+ 'use cache';
+ await new Promise(resolve => setTimeout(resolve, 100));
+ return [1, 2, 3, 4, 5];
+ });
+ };
+
+ const todos = await getTodos();
+
+ return Todos fetched: {todos.length}
;
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/favicon.ico b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/favicon.ico
new file mode 100644
index 000000000000..718d6fea4835
Binary files /dev/null and b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/favicon.ico differ
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/global-error.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/global-error.tsx
new file mode 100644
index 000000000000..20c175015b03
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/global-error.tsx
@@ -0,0 +1,23 @@
+'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-cacheComponents/app/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/layout.tsx
new file mode 100644
index 000000000000..c8f9cee0b787
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/layout.tsx
@@ -0,0 +1,7 @@
+export default function Layout({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/page.tsx
new file mode 100644
index 000000000000..433db8283beb
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/page.tsx
@@ -0,0 +1,3 @@
+export default function Page() {
+ return Next 16 CacheComponents test app
;
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/suspense/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/suspense/page.tsx
new file mode 100644
index 000000000000..32e5a73afc14
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/suspense/page.tsx
@@ -0,0 +1,26 @@
+import { Suspense } from 'react';
+import * as Sentry from '@sentry/nextjs';
+
+export default function Page() {
+ return (
+ <>
+ This will be pre-rendered
+ Loading...}>
+
+
+ >
+ );
+}
+
+async function DynamicContent() {
+ const getTodos = async () => {
+ return Sentry.startSpan({ name: 'getTodos', op: 'get.todos' }, async () => {
+ await new Promise(resolve => setTimeout(resolve, 100));
+ return [1, 2, 3, 4, 5];
+ });
+ };
+
+ const todos = await getTodos();
+
+ return Todos fetched: {todos.length}
;
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/eslint.config.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/eslint.config.mjs
new file mode 100644
index 000000000000..60f7af38f6c2
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/eslint.config.mjs
@@ -0,0 +1,19 @@
+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-cacheComponents/instrumentation-client.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/instrumentation-client.ts
new file mode 100644
index 000000000000..4870c64e7959
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/instrumentation-client.ts
@@ -0,0 +1,11 @@
+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,
+ sendDefaultPii: true,
+});
+
+export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/instrumentation.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/instrumentation.ts
new file mode 100644
index 000000000000..964f937c439a
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/instrumentation.ts
@@ -0,0 +1,13 @@
+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-cacheComponents/next.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/next.config.ts
new file mode 100644
index 000000000000..2841f1c0c5da
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/next.config.ts
@@ -0,0 +1,10 @@
+import { withSentryConfig } from '@sentry/nextjs';
+import type { NextConfig } from 'next';
+
+const nextConfig: NextConfig = {
+ cacheComponents: true,
+};
+
+export default withSentryConfig(nextConfig, {
+ silent: true,
+});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/package.json
new file mode 100644
index 000000000000..de2d67b0ed4b
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/package.json
@@ -0,0 +1,51 @@
+{
+ "name": "nextjs-16-cacheComponents",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "clean": "npx rimraf node_modules pnpm-lock.yaml .tmp_dev_server_logs",
+ "dev:webpack": "next dev --webpack",
+ "build-webpack": "next build --webpack",
+ "start": "next start",
+ "lint": "eslint",
+ "test:prod": "TEST_ENV=production playwright test",
+ "test:dev": "TEST_ENV=development playwright test",
+ "test:dev-webpack": "TEST_ENV=development-webpack playwright test",
+ "test:build": "pnpm install && pnpm build",
+ "test:build-webpack": "pnpm install && pnpm build-webpack",
+ "test:build-canary": "pnpm install && pnpm add next@canary && pnpm build",
+ "test:build-latest": "pnpm install && pnpm add next@latest && pnpm build",
+ "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"
+ },
+ "dependencies": {
+ "@sentry/nextjs": "latest || *",
+ "@sentry/core": "latest || *",
+ "import-in-the-middle": "^1",
+ "next": "16.0.0",
+ "react": "19.1.0",
+ "react-dom": "19.1.0",
+ "require-in-the-middle": "^7",
+ "zod": "^3.22.4"
+ },
+ "devDependencies": {
+ "@playwright/test": "~1.53.2",
+ "@sentry-internal/test-utils": "link:../../../test-utils",
+ "@types/node": "^20",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "eslint": "^9",
+ "eslint-config-next": "canary",
+ "typescript": "^5"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ },
+ "sentryTest": {
+ "//": "TODO: Add variants for webpack once supported"
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/playwright.config.mjs
new file mode 100644
index 000000000000..797418b8cf7d
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/playwright.config.mjs
@@ -0,0 +1,29 @@
+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-webpack') {
+ return 'pnpm next dev -p 3030 --webpack 2>&1 | tee .tmp_dev_server_logs';
+ }
+
+ if (testEnv === 'development') {
+ return 'pnpm next dev -p 3030 2>&1 | tee .tmp_dev_server_logs';
+ }
+
+ if (testEnv === 'production') {
+ return 'pnpm 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-cacheComponents/proxy.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/proxy.ts
new file mode 100644
index 000000000000..60722f329fa0
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/proxy.ts
@@ -0,0 +1,24 @@
+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
+
+ 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-cacheComponents/public/file.svg b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/public/file.svg
new file mode 100644
index 000000000000..004145cddf3f
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/public/file.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/public/globe.svg b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/public/globe.svg
new file mode 100644
index 000000000000..567f17b0d7c7
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/public/globe.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/public/next.svg b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/public/next.svg
new file mode 100644
index 000000000000..5174b28c565c
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/public/vercel.svg b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/public/vercel.svg
new file mode 100644
index 000000000000..77053960334e
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/public/vercel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/public/window.svg b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/public/window.svg
new file mode 100644
index 000000000000..b2b2a44f6ebc
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/public/window.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/sentry.edge.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/sentry.edge.config.ts
new file mode 100644
index 000000000000..2199afc46eaf
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/sentry.edge.config.ts
@@ -0,0 +1,10 @@
+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,
+ sendDefaultPii: true,
+ // debug: true,
+});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/sentry.server.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/sentry.server.config.ts
new file mode 100644
index 000000000000..08d5d580b314
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/sentry.server.config.ts
@@ -0,0 +1,11 @@
+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,
+ sendDefaultPii: true,
+ // debug: true,
+ integrations: [Sentry.vercelAIIntegration()],
+});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/start-event-proxy.mjs
new file mode 100644
index 000000000000..f0fae444f2eb
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/start-event-proxy.mjs
@@ -0,0 +1,14 @@
+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-cacheComponents',
+ envelopeDumpPath: path.join(
+ process.cwd(),
+ `event-dumps/next-16-cacheComponents-v${packageJson.dependencies.next}-${process.env.TEST_ENV}.dump`,
+ ),
+});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tests/cacheComponents.spec.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tests/cacheComponents.spec.ts
new file mode 100644
index 000000000000..9f7b0ca559be
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tests/cacheComponents.spec.ts
@@ -0,0 +1,28 @@
+import { expect, test } from '@playwright/test';
+import { waitForTransaction } from '@sentry-internal/test-utils';
+
+test('Should render cached component', async ({ page }) => {
+ const serverTxPromise = waitForTransaction('nextjs-16-cacheComponents', async transactionEvent => {
+ return transactionEvent.contexts?.trace?.op === 'http.server';
+ });
+
+ await page.goto('/cache');
+ const serverTx = await serverTxPromise;
+
+ // we want to skip creating spans in cached environments
+ expect(serverTx.spans?.filter(span => span.op === 'get.todos')).toHaveLength(0);
+ await expect(page.locator('#todos-fetched')).toHaveText('Todos fetched: 5');
+});
+
+test('Should render suspense component', async ({ page }) => {
+ const serverTxPromise = waitForTransaction('nextjs-16-cacheComponents', async transactionEvent => {
+ return transactionEvent.contexts?.trace?.op === 'http.server';
+ });
+
+ await page.goto('/suspense');
+ const serverTx = await serverTxPromise;
+
+ // this will be called several times in development mode, so we need to check for at least one span
+ expect(serverTx.spans?.filter(span => span.op === 'get.todos').length).toBeGreaterThan(0);
+ await expect(page.locator('#todos-fetched')).toHaveText('Todos fetched: 5');
+});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tsconfig.json b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tsconfig.json
new file mode 100644
index 000000000000..cc9ed39b5aa2
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "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/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts
index a171652b7221..9cc8d3b10bfe 100644
--- a/packages/nextjs/src/client/index.ts
+++ b/packages/nextjs/src/client/index.ts
@@ -14,6 +14,9 @@ import { applyTunnelRouteOption } from './tunnelRoute';
export * from '@sentry/react';
export * from '../common';
export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error';
+
+// Override core span methods with Next.js-specific implementations that support Cache Components
+export { startSpan, startSpanManual, startInactiveSpan } from '../common/utils/nextSpan';
export { browserTracingIntegration } from './browserTracingIntegration';
export { captureRouterTransitionStart } from './routing/appRouterRoutingInstrumentation';
diff --git a/packages/nextjs/src/common/utils/isUseCacheFunction.ts b/packages/nextjs/src/common/utils/isUseCacheFunction.ts
new file mode 100644
index 000000000000..fb392c18c125
--- /dev/null
+++ b/packages/nextjs/src/common/utils/isUseCacheFunction.ts
@@ -0,0 +1,58 @@
+// Vendored from: https://github.com/vercel/next.js/blob/canary/packages/next/src/lib/client-and-server-references.ts
+
+interface ServerReferenceInfo {
+ type: 'server-action' | 'use-cache';
+ usedArgs: [boolean, boolean, boolean, boolean, boolean, boolean];
+ hasRestArgs: boolean;
+}
+
+export interface ServerReference {
+ $$typeof: symbol;
+ $$id: string;
+}
+
+export type ServerFunction = ServerReference & ((...args: unknown[]) => Promise);
+
+function extractInfoFromServerReferenceId(id: string): ServerReferenceInfo {
+ const infoByte = parseInt(id.slice(0, 2), 16);
+ // eslint-disable-next-line no-bitwise
+ const typeBit = (infoByte >> 7) & 0x1;
+ // eslint-disable-next-line no-bitwise
+ const argMask = (infoByte >> 1) & 0x3f;
+ // eslint-disable-next-line no-bitwise
+ const restArgs = infoByte & 0x1;
+ const usedArgs = Array(6);
+
+ for (let index = 0; index < 6; index++) {
+ const bitPosition = 5 - index;
+ // eslint-disable-next-line no-bitwise
+ const bit = (argMask >> bitPosition) & 0x1;
+ usedArgs[index] = bit === 1;
+ }
+
+ return {
+ type: typeBit === 1 ? 'use-cache' : 'server-action',
+ usedArgs: usedArgs as [boolean, boolean, boolean, boolean, boolean, boolean],
+ hasRestArgs: restArgs === 1,
+ };
+}
+
+function isServerReference(value: T & Partial): value is T & ServerFunction {
+ return value.$$typeof === Symbol.for('react.server.reference');
+}
+
+/**
+ * Check if the function is a use cache function.
+ *
+ * @param value - The function to check.
+ * @returns true if the function is a use cache function, false otherwise.
+ */
+export function isUseCacheFunction(value: T & Partial): value is T & ServerFunction {
+ if (!isServerReference(value)) {
+ return false;
+ }
+
+ const { type } = extractInfoFromServerReferenceId(value.$$id);
+
+ return type === 'use-cache';
+}
diff --git a/packages/nextjs/src/common/utils/nextSpan.ts b/packages/nextjs/src/common/utils/nextSpan.ts
new file mode 100644
index 000000000000..1c9c47119a6d
--- /dev/null
+++ b/packages/nextjs/src/common/utils/nextSpan.ts
@@ -0,0 +1,83 @@
+import type { Span, StartSpanOptions } from '@sentry/core';
+import {
+ debug,
+ SentryNonRecordingSpan,
+ startInactiveSpan as coreStartInactiveSpan,
+ startSpan as coreStartSpan,
+ startSpanManual as coreStartSpanManual,
+} from '@sentry/core';
+import { DEBUG_BUILD } from '../debug-build';
+import { isBuild } from './isBuild';
+import type { ServerReference } from './isUseCacheFunction';
+import { isUseCacheFunction } from './isUseCacheFunction';
+
+function shouldNoopSpan(callback?: T & Partial): boolean {
+ const isBuildContext = isBuild();
+ const isUseCacheFunctionContext = callback ? isUseCacheFunction(callback) : false;
+
+ if (isUseCacheFunctionContext) {
+ DEBUG_BUILD && debug.log('Skipping span creation in Cache Components context');
+ }
+
+ return isBuildContext || isUseCacheFunctionContext;
+}
+
+function createNonRecordingSpan(): Span {
+ return new SentryNonRecordingSpan({
+ traceId: '00000000000000000000000000000000',
+ spanId: '0000000000000000',
+ });
+}
+
+/**
+ * Next.js-specific implementation of `startSpan` that skips span creation
+ * in Cache Components contexts (which render at build time).
+ *
+ * When in a Cache Components context, we execute the callback with a non-recording span
+ * and return early without creating an actual span, since spans don't make sense at build/cache time.
+ *
+ * @param options - Options for starting the span
+ * @param callback - Callback function that receives the span
+ * @returns The return value of the callback
+ */
+export function startSpan(options: StartSpanOptions, callback: (span: Span) => T): T {
+ if (shouldNoopSpan(callback)) {
+ return callback(createNonRecordingSpan());
+ }
+
+ return coreStartSpan(options, callback);
+}
+
+/**
+ *
+ * When in a Cache Components context, we execute the callback with a non-recording span
+ * and return early without creating an actual span, since spans don't make sense at build/cache time.
+ *
+ * @param options - Options for starting the span
+ * @param callback - Callback function that receives the span and finish function
+ * @returns The return value of the callback
+ */
+export function startSpanManual(options: StartSpanOptions, callback: (span: Span, finish: () => void) => T): T {
+ if (shouldNoopSpan(callback)) {
+ const nonRecordingSpan = createNonRecordingSpan();
+ return callback(nonRecordingSpan, () => nonRecordingSpan.end());
+ }
+
+ return coreStartSpanManual(options, callback);
+}
+
+/**
+ *
+ * When in a Cache Components context, we return a non-recording span and return early
+ * without creating an actual span, since spans don't make sense at build/cache time.
+ *
+ * @param options - Options for starting the span
+ * @returns A non-recording span (in Cache Components context) or the created span
+ */
+export function startInactiveSpan(options: StartSpanOptions): Span {
+ if (shouldNoopSpan()) {
+ return createNonRecordingSpan();
+ }
+
+ return coreStartInactiveSpan(options);
+}
diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts
index 091adab98dee..2232d259ec11 100644
--- a/packages/nextjs/src/edge/index.ts
+++ b/packages/nextjs/src/edge/index.ts
@@ -31,6 +31,9 @@ import { distDirRewriteFramesIntegration } from './distDirRewriteFramesIntegrati
export * from '@sentry/vercel-edge';
export * from '../common';
export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error';
+
+// Override core span methods with Next.js-specific implementations that support Cache Components
+export { startSpan, startSpanManual, startInactiveSpan } from '../common/utils/nextSpan';
export { wrapApiHandlerWithSentry } from './wrapApiHandlerWithSentry';
export type EdgeOptions = VercelEdgeOptions;
diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts
index caec9a9f1af1..ca387d752a1d 100644
--- a/packages/nextjs/src/server/index.ts
+++ b/packages/nextjs/src/server/index.ts
@@ -47,6 +47,9 @@ export * from '@sentry/node';
export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error';
+// Override core span methods with Next.js-specific implementations that support Cache Components
+export { startSpan, startSpanManual, startInactiveSpan } from '../common/utils/nextSpan';
+
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
_sentryRewriteFramesDistDir?: string;
_sentryRewritesTunnelPath?: string;