Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
29433d8
docs: add design spec for network transport fallback in isolated serv…
AlemTuzlak Mar 12, 2026
42b3beb
docs: address spec review findings for network transport fallback
AlemTuzlak Mar 12, 2026
ed6e0a7
docs: clarify dual handler paths in network transport spec
AlemTuzlak Mar 12, 2026
c1b7af1
docs: add implementation plan for network transport fallback
AlemTuzlak Mar 12, 2026
76d7711
feat: add eventId and source fields to TanStackDevtoolsEvent interface
AlemTuzlak Mar 12, 2026
b4e9e7d
feat: add server bridge WebSocket connection support to ServerEventBus
AlemTuzlak Mar 12, 2026
884ba69
feat: add source-based routing to POST handlers for server bridge sup…
AlemTuzlak Mar 12, 2026
2d2b0f0
feat: add RingBuffer utility for event ID deduplication
AlemTuzlak Mar 12, 2026
e6a3e57
feat: add network transport detection and compile-time placeholders t…
AlemTuzlak Mar 12, 2026
76907ad
feat: add WebSocket network transport fallback to EventClient
AlemTuzlak Mar 12, 2026
1b7f1a2
fix: improve WebSocket error handling and destroy cleanup in EventClient
AlemTuzlak Mar 12, 2026
f17620a
test: add end-to-end integration tests for network transport fallback
AlemTuzlak Mar 12, 2026
693e472
docs: mark network transport fallback spec as implemented
AlemTuzlak Mar 12, 2026
64ae1bb
ci: apply automated fixes
autofix-ci[bot] Mar 12, 2026
ac41406
feat: add Nitro v3 and Cloudflare Workers test examples
AlemTuzlak Mar 12, 2026
3f82eeb
Merge branch 'main' into worktree-polished-cuddling-lark
AlemTuzlak Mar 13, 2026
c61ea0f
Refactor code structure for improved readability and maintainability
AlemTuzlak Mar 27, 2026
939ac70
Merge branch 'worktree-polished-cuddling-lark' of https://github.com/…
AlemTuzlak Mar 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: add Nitro v3 and Cloudflare Workers test examples
Two minimal examples for manually testing the network transport fallback:

- examples/react/start-nitro — TanStack Start + Nitro v3 (worker threads)
- examples/react/start-cloudflare — TanStack Start + Cloudflare Workers

Both emit devtools events from server functions and display them in a
custom "Server Events" devtools panel. If events appear in the panel,
the network transport fallback is working correctly.
  • Loading branch information
AlemTuzlak committed Mar 12, 2026
commit ac41406ce75fe776852a6523c11014e32a31833a
11 changes: 11 additions & 0 deletions examples/react/start-cloudflare/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
.env
.nitro
.tanstack
.wrangler
.output
.vinxi
32 changes: 32 additions & 0 deletions examples/react/start-cloudflare/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "start-cloudflare",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev --port 3002",
"build": "vite build",
"preview": "vite preview",
"deploy": "npm run build && wrangler deploy"
},
"dependencies": {
"@cloudflare/vite-plugin": "^1.13.8",
"@tanstack/devtools-event-client": "workspace:*",
"@tanstack/react-devtools": "workspace:*",
"@tanstack/react-router": "^1.132.0",
"@tanstack/react-start": "^1.132.0",
"@tanstack/router-plugin": "^1.132.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"vite-tsconfig-paths": "^6.0.2"
},
"devDependencies": {
"@tanstack/devtools-vite": "workspace:*",
"@types/node": "^22.15.2",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@vitejs/plugin-react": "^5.0.4",
"typescript": "~5.9.2",
"vite": "^7.1.7",
"wrangler": "^4.40.3"
}
}
162 changes: 162 additions & 0 deletions examples/react/start-cloudflare/src/devtools/ServerEventsPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { useEffect, useState } from 'react'
import { serverEventClient } from './server-event-client'
import type { ServerEvent } from './server-event-client'

export function ServerEventsPanel() {
const [events, setEvents] = useState<Array<ServerEvent>>([])

useEffect(() => {
const cleanup = serverEventClient.on(
'server-fn-called',
(event) => {
setEvents((prev) => [event.payload, ...prev].slice(0, 100))
},
{ withEventTarget: true },
)

return cleanup
}, [])

const formatTime = (timestamp: number) => {
return new Date(timestamp).toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
fractionalSecondDigits: 3,
})
}

return (
<div
style={{
padding: '16px',
fontFamily: 'system-ui, sans-serif',
height: '100%',
overflow: 'auto',
}}
>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '16px',
}}
>
<h2
style={{
margin: 0,
fontSize: '16px',
fontWeight: 600,
color: 'inherit',
}}
>
Server Events ({events.length})
</h2>
<button
onClick={() => setEvents([])}
style={{
padding: '4px 12px',
fontSize: '12px',
borderRadius: '4px',
border: '1px solid currentColor',
background: 'transparent',
color: 'inherit',
cursor: 'pointer',
opacity: 0.7,
}}
>
Clear
</button>
</div>

<div
style={{
padding: '12px',
borderRadius: '8px',
background: 'rgba(249, 115, 22, 0.1)',
border: '1px solid rgba(249, 115, 22, 0.3)',
marginBottom: '16px',
fontSize: '13px',
lineHeight: 1.5,
}}
>
These events are emitted from <strong>server functions</strong> running
in Cloudflare Workers' isolated environment. If you see events appearing
here, the network transport fallback is working correctly.
</div>

{events.length === 0 ? (
<div
style={{
padding: '24px',
textAlign: 'center',
opacity: 0.5,
fontSize: '14px',
}}
>
No server events yet.
<br />
Click "Call Server Function" to emit an event.
</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{events.map((ev, index) => (
<div
key={`${ev.timestamp}-${index}`}
style={{
padding: '12px',
borderRadius: '8px',
background: 'rgba(128, 128, 128, 0.1)',
border: '1px solid rgba(128, 128, 128, 0.2)',
}}
>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: '4px',
}}
>
<span
style={{
fontWeight: 600,
fontSize: '14px',
color: '#f97316',
}}
>
{ev.name}
</span>
<span
style={{
fontSize: '11px',
opacity: 0.6,
fontFamily: 'monospace',
}}
>
{formatTime(ev.timestamp)}
</span>
</div>
{ev.data !== undefined && (
<pre
style={{
fontSize: '12px',
fontFamily: 'monospace',
opacity: 0.8,
margin: 0,
whiteSpace: 'pre-wrap',
wordBreak: 'break-all',
}}
>
{JSON.stringify(ev.data, null, 2)}
</pre>
)}
</div>
))}
</div>
)}
</div>
)
}
2 changes: 2 additions & 0 deletions examples/react/start-cloudflare/src/devtools/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { ServerEventsPanel } from './ServerEventsPanel'
export { emitServerEvent } from './server-event-client'
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { EventClient } from '@tanstack/devtools-event-client'

export interface ServerEvent {
name: string
timestamp: number
data?: unknown
}

type ServerEventMap = {
'server-fn-called': ServerEvent
}

class ServerEventClient extends EventClient<ServerEventMap> {
constructor() {
super({
pluginId: 'server-events',
})
}
}

export const serverEventClient = new ServerEventClient()

/**
* Emit a devtools event from a server function.
* In Cloudflare Workers, server functions run in an isolated environment.
* Without the network transport fallback, these events would be lost.
*/
export function emitServerEvent(name: string, data?: unknown) {
if (process.env.NODE_ENV !== 'development') return

serverEventClient.emit('server-fn-called', {
name,
timestamp: Date.now(),
data,
})
}
13 changes: 13 additions & 0 deletions examples/react/start-cloudflare/src/router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export const getRouter = () => {
const router = createRouter({
routeTree,
context: {},
scrollRestoration: true,
defaultPreloadStaleTime: 0,
})

return router
}
38 changes: 38 additions & 0 deletions examples/react/start-cloudflare/src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
import { TanStackDevtools } from '@tanstack/react-devtools'
import { ServerEventsPanel } from '../devtools'

export const Route = createRootRoute({
head: () => ({
meta: [
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ title: 'Cloudflare Workers Devtools Test' },
],
}),
shellComponent: RootDocument,
})

function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<HeadContent />
</head>
<body style={{ margin: 0, fontFamily: 'system-ui, sans-serif' }}>
{children}
<TanStackDevtools
config={{ position: 'bottom-right' }}
plugins={[
{
id: 'server-events',
name: 'Server Events',
render: <ServerEventsPanel />,
},
]}
/>
<Scripts />
</body>
</html>
)
}
Loading
Loading