-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
test(tanstackstart): Add TanStack Start on Cloudflare Workers test app #20649
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
8dd6f21
2d2ea64
59f40bc
7ee2bb7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| node_modules | ||
| dist | ||
| .output | ||
| .wrangler | ||
| .tanstack | ||
| src/routeTree.gen.ts |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| { | ||
| "name": "tanstackstart-react-cloudflare", | ||
| "private": true, | ||
| "version": "0.0.1", | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite dev", | ||
| "build": "vite build", | ||
| "preview": "wrangler dev --var E2E_TEST_DSN:$E2E_TEST_DSN --log-level=$(test $CI && echo 'none' || echo 'log')", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preview bypasses built appMedium Severity The Reviewed by Cursor Bugbot for commit d4d6179. Configure here. |
||
| "test": "playwright test", | ||
| "typecheck": "tsc --noEmit", | ||
| "test:build": "pnpm install && pnpm build", | ||
| "test:assert": "pnpm typecheck && pnpm test" | ||
| }, | ||
| "dependencies": { | ||
| "@sentry/browser": "file:../../packed/sentry-browser-packed.tgz", | ||
| "@sentry/cloudflare": "file:../../packed/sentry-cloudflare-packed.tgz", | ||
| "@sentry/tanstackstart-react": "file:../../packed/sentry-tanstackstart-react-packed.tgz", | ||
| "@tanstack/react-start": "^1.136.0", | ||
| "@tanstack/react-router": "^1.136.0", | ||
| "react": "^19.2.0", | ||
| "react-dom": "^19.2.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/vite-plugin": "^1.35.0", | ||
| "@cloudflare/workers-types": "^4.20260504.0", | ||
| "@playwright/test": "~1.56.0", | ||
| "@sentry-internal/test-utils": "link:../../../test-utils", | ||
| "@types/react": "^19.2.0", | ||
| "@types/react-dom": "^19.2.0", | ||
| "@vitejs/plugin-react": "^4.5.0", | ||
| "typescript": "^5.9.0", | ||
| "vite": "7.3.1", | ||
| "vite-tsconfig-paths": "^5.1.4", | ||
| "wrangler": "^4.68.1" | ||
| }, | ||
| "volta": { | ||
| "node": "24.15.0", | ||
| "extends": "../../package.json" | ||
| }, | ||
| "sentryTest": { | ||
| "optional": true | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { getPlaywrightConfig } from '@sentry-internal/test-utils'; | ||
|
|
||
| export default getPlaywrightConfig({ | ||
| startCommand: 'pnpm preview', | ||
| port: 8787, | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| interface Env { | ||
| E2E_TEST_DSN: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import * as Sentry from '@sentry/browser'; | ||
| import { createRouter } from '@tanstack/react-router'; | ||
| import { routeTree } from './routeTree.gen'; | ||
|
|
||
| export const getRouter = () => { | ||
| const router = createRouter({ | ||
| routeTree, | ||
| scrollRestoration: true, | ||
| }); | ||
|
|
||
| if (!router.isServer) { | ||
| Sentry.init({ | ||
| environment: 'qa', | ||
| dsn: 'https://public@dsn.ingest.sentry.io/1337', | ||
| integrations: [Sentry.browserTracingIntegration()], | ||
| tracesSampleRate: 1.0, | ||
| release: 'e2e-test', | ||
| tunnel: 'http://localhost:3031/', | ||
| }); | ||
| } | ||
|
|
||
| return router; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import type { ReactNode } from 'react'; | ||
| import { Outlet, createRootRoute, HeadContent, Scripts } from '@tanstack/react-router'; | ||
| import { getTraceData } from '@sentry/tanstackstart-react'; | ||
|
|
||
| export const Route = createRootRoute({ | ||
| head: () => { | ||
| const traceData = getTraceData(); | ||
| const sentryMeta = Object.entries(traceData).map(([key, value]) => ({ | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if this is the best way in TanStack to add trace propagation, couldn't find anything specific in the docs
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have never looked into this, does that work? If yes that's amazing then we can document how to set that up :)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it works - but I'm not sure if it is a hack of mine of the correct way of doing it. In any way, I can add the docs for it |
||
| name: key, | ||
| content: value, | ||
| })); | ||
|
|
||
| return { | ||
| meta: [ | ||
| { | ||
| charSet: 'utf-8', | ||
| }, | ||
| { | ||
| name: 'viewport', | ||
| content: 'width=device-width, initial-scale=1', | ||
| }, | ||
| { | ||
| title: 'TanStack Start Cloudflare E2E Test', | ||
| }, | ||
| ...sentryMeta, | ||
| ], | ||
| }; | ||
| }, | ||
| component: RootComponent, | ||
| }); | ||
|
|
||
| function RootComponent() { | ||
| return ( | ||
| <RootDocument> | ||
| <Outlet /> | ||
| </RootDocument> | ||
| ); | ||
| } | ||
|
|
||
| function RootDocument({ children }: Readonly<{ children: ReactNode }>) { | ||
| return ( | ||
| <html> | ||
| <head> | ||
| <HeadContent /> | ||
| </head> | ||
| <body> | ||
| {children} | ||
| <Scripts /> | ||
| </body> | ||
| </html> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { createFileRoute } from '@tanstack/react-router'; | ||
|
|
||
| export const Route = createFileRoute('/api/error')({ | ||
| server: { | ||
| handlers: { | ||
| GET: async () => { | ||
| throw new Error('Sentry API Route Test Error'); | ||
| }, | ||
| }, | ||
| }, | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { createFileRoute } from '@tanstack/react-router'; | ||
| import { flush } from '@sentry/cloudflare'; | ||
|
|
||
| export const Route = createFileRoute('/api/flush')({ | ||
| server: { | ||
| handlers: { | ||
| GET: async () => { | ||
| await flush(); | ||
| return new Response('ok'); | ||
| }, | ||
| }, | ||
| }, | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { createFileRoute } from '@tanstack/react-router'; | ||
| import { createServerFn } from '@tanstack/react-start'; | ||
|
|
||
| const throwServerError = createServerFn().handler(async () => { | ||
| throw new Error('Sentry Server Function Test Error'); | ||
| }); | ||
|
|
||
| export const Route = createFileRoute('/')({ | ||
| component: Home, | ||
| }); | ||
|
|
||
| function Home() { | ||
| return ( | ||
| <div> | ||
| <h1>TanStack Start Cloudflare E2E Test</h1> | ||
| <button | ||
| id="client-error-btn" | ||
| type="button" | ||
| onClick={() => { | ||
| throw new Error('Sentry Client Test Error'); | ||
| }} | ||
| > | ||
| Break the client | ||
| </button> | ||
| <button | ||
| id="throw-server-fn-btn" | ||
| type="button" | ||
| onClick={async () => { | ||
| await throwServerError(); | ||
| }} | ||
| > | ||
| Break server function | ||
| </button> | ||
| <button | ||
| id="api-error-btn" | ||
| type="button" | ||
| onClick={async () => { | ||
| await fetch('/api/error'); | ||
| }} | ||
| > | ||
| Break API route | ||
| </button> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { createFileRoute } from '@tanstack/react-router'; | ||
|
|
||
| export const Route = createFileRoute('/ssr-error')({ | ||
| loader: () => { | ||
| throw new Error('Sentry SSR Test Error'); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. l: this as well as the flush endpoint are not used I think? in other Tanstack Start apps we have a test that asserts that ssr errors are not captured (where this is used). fine to leave this out for this one I think but then we could trim down the application a bit (i.e. remove the flush endpoint and ssr error)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great catch. I removed the test because it originally didn't work because of the missing middleware. After readding the middleware I forgot to readd the test. |
||
| }, | ||
| component: () => <div>SSR Error Page</div>, | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import * as Sentry from '@sentry/cloudflare'; | ||
| import { createFileRoute } from '@tanstack/react-router'; | ||
| import { createServerFn } from '@tanstack/react-start'; | ||
|
|
||
| const testLog = createServerFn().handler(async () => { | ||
| console.log('Test log from server function'); | ||
| return { message: 'Log created' }; | ||
| }); | ||
|
|
||
| const testNestedLog = createServerFn().handler(async () => { | ||
| await Sentry.startSpan({ name: 'testNestedLog' }, async () => { | ||
| await testLog(); | ||
| }); | ||
|
|
||
| console.log('Outer test log from server function'); | ||
| return { message: 'Nested log created' }; | ||
| }); | ||
|
|
||
| export const Route = createFileRoute('/test-serverFn')({ | ||
| component: TestServerFn, | ||
| }); | ||
|
|
||
| function TestServerFn() { | ||
| return ( | ||
| <div> | ||
| <h1>Test Server Function Page</h1> | ||
| <button | ||
| id="server-fn-btn" | ||
| type="button" | ||
| onClick={async () => { | ||
| await testLog(); | ||
| }} | ||
| > | ||
| Call server function | ||
| </button> | ||
| <button | ||
| id="server-fn-nested-btn" | ||
| type="button" | ||
| onClick={async () => { | ||
| await testNestedLog(); | ||
| }} | ||
| > | ||
| Call server function nested | ||
| </button> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import * as Sentry from '@sentry/cloudflare'; | ||
| import { wrapFetchWithSentry } from '@sentry/tanstackstart-react'; | ||
| import handler from '@tanstack/react-start/server-entry'; | ||
|
|
||
| export default Sentry.withSentry( | ||
| (env: Env) => ({ | ||
| dsn: env.E2E_TEST_DSN, | ||
| tunnel: 'http://localhost:3031/', | ||
| tracesSampleRate: 1.0, | ||
| environment: 'qa', | ||
| }), | ||
| // @ts-expect-error - handler is not typed as a Cloudflare handler | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the type error |
||
| wrapFetchWithSentry(handler), | ||
| ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { sentryGlobalFunctionMiddleware, sentryGlobalRequestMiddleware } from '@sentry/tanstackstart-react'; | ||
| import { createStart } from '@tanstack/react-start'; | ||
|
|
||
| export const startInstance = createStart(() => { | ||
| return { | ||
| requestMiddleware: [sentryGlobalRequestMiddleware], | ||
| functionMiddleware: [sentryGlobalFunctionMiddleware], | ||
| }; | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { startEventProxyServer } from '@sentry-internal/test-utils'; | ||
|
|
||
| startEventProxyServer({ | ||
| port: 3031, | ||
| proxyServerName: 'tanstackstart-react-cloudflare', | ||
| }); |


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing quotes around
--varvalue in preview scriptMedium Severity
The
previewscript passes--var E2E_TEST_DSN:$E2E_TEST_DSNwithout quotes around the value. Every other Cloudflare test app in the repo (cloudflare-workers,cloudflare-local-workers,cloudflare-mcp,cloudflare-workersentrypoint,astro-6-cf-workers) uses the quoted form--var \"E2E_TEST_DSN:$E2E_TEST_DSN\". Since DSN values contain shell-sensitive characters like://and@, the unquoted expansion could cause parsing issues depending on the shell environment in CI.Reviewed by Cursor Bugbot for commit 8dd6f21. Configure here.