From e23013e0febb4a8e3d9cfe34b99414e8b66586b5 Mon Sep 17 00:00:00 2001 From: Sora Morimoto Date: Sat, 21 Feb 2026 23:42:20 +0900 Subject: [PATCH 1/3] Add workerd and worker export conditions to tanstackstart-react On Cloudflare Workers the "browser" exports condition is matched, resolving the package entry to index.client.js. TanStack Start's import-protection plugin denies files matching **/*.client.* in the server environment, causing the build to fail. Add "workerd" and "worker" conditions before "browser" so that bundlers targeting Cloudflare Workers (e.g. @cloudflare/vite-plugin) resolve to index.server.js instead, avoiding the false positive. This follows the same pattern already used by @sentry/nextjs, @sentry/sveltekit, and @sentry/remix. --- packages/tanstackstart-react/package.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index 48515847c792..b3f0a20fa094 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -19,6 +19,14 @@ "./package.json": "./package.json", ".": { "types": "./build/types/index.types.d.ts", + "workerd": { + "import": "./build/esm/index.server.js", + "require": "./build/cjs/index.server.js" + }, + "worker": { + "import": "./build/esm/index.server.js", + "require": "./build/cjs/index.server.js" + }, "browser": { "import": "./build/esm/index.client.js", "require": "./build/cjs/index.client.js" From 19235aaf5db932086903fd68b00e7701beb6a54d Mon Sep 17 00:00:00 2001 From: Sora Morimoto Date: Sun, 22 Feb 2026 00:32:43 +0900 Subject: [PATCH 2/3] Add Cloudflare Workers entrypoint for tanstackstart-react The workerd/worker export conditions were pointing to the Node.js server entry which depends on @sentry/node and fails in Workers runtimes. This adds a dedicated cloudflare entrypoint (following the same pattern as remix and react-router packages) that re-exports framework utilities from @sentry/core instead, making the SDK compatible with Cloudflare Workers. Server module imports are also changed from @sentry/node to @sentry/core since all used APIs are available in both. --- packages/tanstackstart-react/package.json | 16 ++++++--- .../tanstackstart-react/rollup.npm.config.mjs | 8 ++++- .../src/cloudflare/index.ts | 36 +++++++++++++++++++ .../src/server/middleware.ts | 5 ++- .../tanstackstart-react/src/server/utils.ts | 2 +- .../src/server/wrapFetchWithSentry.ts | 2 +- 6 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 packages/tanstackstart-react/src/cloudflare/index.ts diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index b3f0a20fa094..62ac17cfc5d1 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -20,12 +20,12 @@ ".": { "types": "./build/types/index.types.d.ts", "workerd": { - "import": "./build/esm/index.server.js", - "require": "./build/cjs/index.server.js" + "import": "./build/esm/cloudflare/index.js", + "require": "./build/cjs/cloudflare/index.js" }, "worker": { - "import": "./build/esm/index.server.js", - "require": "./build/cjs/index.server.js" + "import": "./build/esm/cloudflare/index.js", + "require": "./build/cjs/cloudflare/index.js" }, "browser": { "import": "./build/esm/index.client.js", @@ -36,6 +36,12 @@ "require": "./build/cjs/index.server.js" } }, + "./cloudflare": { + "types": "./build/types/cloudflare/index.d.ts", + "import": "./build/esm/cloudflare/index.js", + "require": "./build/cjs/cloudflare/index.js", + "default": "./build/esm/cloudflare/index.js" + }, "./import": { "import": { "default": "./build/import-hook.mjs" @@ -80,7 +86,7 @@ "build:dev:watch": "yarn build:watch", "build:transpile:watch": "nodemon --ext ts --watch src scripts/buildRollup.ts", "build:tarball": "npm pack", - "circularDepCheck": "madge --circular src/index.client.ts && madge --circular src/index.server.ts && madge --circular src/index.types.ts", + "circularDepCheck": "madge --circular src/index.client.ts && madge --circular src/index.server.ts && madge --circular src/index.types.ts && madge --circular src/cloudflare/index.ts", "clean": "rimraf build coverage sentry-tanstackstart-react-*.tgz", "fix": "eslint . --format stylish --fix", "lint": "eslint . --format stylish", diff --git a/packages/tanstackstart-react/rollup.npm.config.mjs b/packages/tanstackstart-react/rollup.npm.config.mjs index 3c1bae9abd07..236262ed85f7 100644 --- a/packages/tanstackstart-react/rollup.npm.config.mjs +++ b/packages/tanstackstart-react/rollup.npm.config.mjs @@ -3,7 +3,13 @@ import { makeBaseNPMConfig, makeNPMConfigVariants, makeOtelLoaders } from '@sent export default [ ...makeNPMConfigVariants( makeBaseNPMConfig({ - entrypoints: ['src/index.server.ts', 'src/index.client.ts', 'src/client/index.ts', 'src/server/index.ts'], + entrypoints: [ + 'src/index.server.ts', + 'src/index.client.ts', + 'src/client/index.ts', + 'src/server/index.ts', + 'src/cloudflare/index.ts', + ], }), ), ...makeOtelLoaders('./build', 'sentry-node'), diff --git a/packages/tanstackstart-react/src/cloudflare/index.ts b/packages/tanstackstart-react/src/cloudflare/index.ts new file mode 100644 index 000000000000..0a0df6863dbf --- /dev/null +++ b/packages/tanstackstart-react/src/cloudflare/index.ts @@ -0,0 +1,36 @@ +// import/export got a false positive, and affects most of our index barrel files +// can be removed once following issue is fixed: https://github.com/import-js/eslint-plugin-import/issues/703 +/* eslint-disable import/export */ +export * from '../client'; +export * from '../common'; + +export { wrapFetchWithSentry } from '../server/wrapFetchWithSentry'; +export { wrapMiddlewaresWithSentry } from '../server/middleware'; +export { sentryGlobalRequestMiddleware, sentryGlobalFunctionMiddleware } from '../server/globalMiddleware'; + +/** + * A passthrough error boundary for the server that doesn't depend on any react. Error boundaries don't catch SSR errors + * so they should simply be a passthrough. + */ +export const ErrorBoundary = (props: React.PropsWithChildren): React.ReactNode => { + if (!props.children) { + return null; + } + + if (typeof props.children === 'function') { + return (props.children as () => React.ReactNode)(); + } + + return props.children; +}; + +/** + * A passthrough error boundary wrapper for the server that doesn't depend on any react. Error boundaries don't catch + * SSR errors so they should simply be a passthrough. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function withErrorBoundary

>( + WrappedComponent: React.ComponentType

, +): React.FC

{ + return WrappedComponent as React.FC

; +} diff --git a/packages/tanstackstart-react/src/server/middleware.ts b/packages/tanstackstart-react/src/server/middleware.ts index 9347e6760742..348fb74384c7 100644 --- a/packages/tanstackstart-react/src/server/middleware.ts +++ b/packages/tanstackstart-react/src/server/middleware.ts @@ -1,6 +1,5 @@ -import { addNonEnumerableProperty } from '@sentry/core'; -import type { Span } from '@sentry/node'; -import { getActiveSpan, startSpanManual, withActiveSpan } from '@sentry/node'; +import type { Span } from '@sentry/core'; +import { addNonEnumerableProperty, getActiveSpan, startSpanManual, withActiveSpan } from '@sentry/core'; import type { MiddlewareWrapperOptions, TanStackMiddlewareBase } from '../common/types'; import { getMiddlewareSpanOptions } from './utils'; diff --git a/packages/tanstackstart-react/src/server/utils.ts b/packages/tanstackstart-react/src/server/utils.ts index 2a94cb79deac..fcbae62c2142 100644 --- a/packages/tanstackstart-react/src/server/utils.ts +++ b/packages/tanstackstart-react/src/server/utils.ts @@ -1,5 +1,5 @@ import type { StartSpanOptions } from '@sentry/core'; -import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/node'; +import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; /** * Extracts the SHA-256 hash from a server function pathname. diff --git a/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts b/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts index 22d218ef0b48..635bc75c8779 100644 --- a/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts +++ b/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts @@ -1,4 +1,4 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/node'; +import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core'; import { extractServerFunctionSha256 } from './utils'; export type ServerEntry = { From e47b390586562c62f68764f23bc93021beef1ed0 Mon Sep 17 00:00:00 2001 From: Sora Morimoto Date: Sun, 22 Feb 2026 01:25:38 +0900 Subject: [PATCH 3/3] test(tanstackstart-react): Add unit tests for Cloudflare Workers entrypoint Verify that the cloudflare entrypoint exports server implementations (not client no-op stubs) and passthrough ErrorBoundary/withErrorBoundary. --- .../test/cloudflare/index.test.ts | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 packages/tanstackstart-react/test/cloudflare/index.test.ts diff --git a/packages/tanstackstart-react/test/cloudflare/index.test.ts b/packages/tanstackstart-react/test/cloudflare/index.test.ts new file mode 100644 index 000000000000..db17d528e308 --- /dev/null +++ b/packages/tanstackstart-react/test/cloudflare/index.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from 'vitest'; +import * as CloudflareExports from '../../src/cloudflare/index'; + +describe('Cloudflare entrypoint', () => { + describe('exports', () => { + it('exports wrapMiddlewaresWithSentry as a function (server implementation)', () => { + expect(typeof CloudflareExports.wrapMiddlewaresWithSentry).toBe('function'); + + // The server implementation wraps middlewares with Sentry instrumentation. + // Verify it handles an empty object correctly (server impl returns an empty array). + const result = CloudflareExports.wrapMiddlewaresWithSentry({}); + expect(result).toEqual([]); + }); + + it('exports sentryGlobalRequestMiddleware as a middleware object with server handler', () => { + const middleware = CloudflareExports.sentryGlobalRequestMiddleware; + expect(middleware).toBeDefined(); + // The server implementation has a server handler function, unlike the client no-op stub. + expect(typeof middleware.options.server).toBe('function'); + }); + + it('exports sentryGlobalFunctionMiddleware as a middleware object with server handler', () => { + const middleware = CloudflareExports.sentryGlobalFunctionMiddleware; + expect(middleware).toBeDefined(); + // The server implementation has a server handler function, unlike the client no-op stub. + expect(typeof middleware.options.server).toBe('function'); + }); + + it('exports wrapFetchWithSentry', () => { + expect(typeof CloudflareExports.wrapFetchWithSentry).toBe('function'); + }); + }); + + describe('ErrorBoundary', () => { + it('is a passthrough that returns children directly', () => { + const children = 'test child'; + const result = CloudflareExports.ErrorBoundary({ children }); + expect(result).toBe('test child'); + }); + + it('returns null when no children are provided', () => { + const result = CloudflareExports.ErrorBoundary({}); + expect(result).toBeNull(); + }); + + it('calls children when children is a function', () => { + const children = () => 'function result'; + const result = CloudflareExports.ErrorBoundary({ children }); + expect(result).toBe('function result'); + }); + }); + + describe('withErrorBoundary', () => { + it('is a passthrough that returns the original component', () => { + const MockComponent = () => null; + const result = CloudflareExports.withErrorBoundary(MockComponent); + expect(result).toBe(MockComponent); + }); + }); +});