This project demonstrates multi-worker development with Cloudflare Workers, showcasing how to run multiple Workers locally using separate dev commands.
- Backend Worker (
backend/): A Cloudflare Worker with Durable Objects (TestDurableObject) - Frontend Worker (
frontend/): A SvelteKit application deployed as a Cloudflare Worker that accesses the backend's Durable Objects viascript_namebinding
The frontend references the backend's Durable Object namespace using the script_name configuration, allowing it to access Durable Objects defined in the backend Worker.
Install dependencies and generate Cloudflare types in both folders:
# Backend
cd backend
pnpm install
pnpm cf-typegen
# Frontend
cd frontend
pnpm install
pnpm cf-typegenRun both Workers in separate terminals:
# Terminal 1 - Backend (runs with wrangler dev)
cd backend
pnpm run dev
# Terminal 2 - Frontend (runs with vite dev)
cd frontend
pnpm run devThe backend runs using wrangler dev, while the frontend runs using vite dev (via the Cloudflare Vite plugin). This approach uses the "multiple dev commands" pattern, where each Worker runs independently and can communicate via service bindings or Durable Object bindings across Workers.
The backend worker requires NODE_ENV to be set for security. Direct WebSocket access to /ws/connect is only allowed when NODE_ENV is 'development' or 'test'. In production, WebSocket connections must go through the frontend SvelteKit handler which validates authorization.
Set NODE_ENV in backend/wrangler.jsonc:
Or use a .dev.vars file in the backend/ directory:
cd backend
cat > .dev.vars << EOF
NODE_ENV=development
EOFNote: .dev.vars files are gitignored and only used in local development. For production deployments, set environment variables via wrangler secret put or the Cloudflare dashboard.
The main page includes a WebSocket countdown demo that demonstrates real-time communication using Cloudflare Durable Objects.
- Click "Start Countdown" - triggers a form action that sends 11 messages (10 to 0) spaced 1 second apart
- Messages broadcast via WebSocket - the
MessageCoordinatorDurable Object broadcasts to all connected clients - Real-time display - the UI updates as each countdown message arrives
Browser ←WebSocket→ MessageCoordinator (Durable Object) ←POST→ SvelteKit Action
frontend/src/lib/websocket.ts- WebSocket client with auto-reconnectfrontend/src/lib/components/WebSocketStatus.svelte- Connection status indicator (red/amber/green)backend/src/message-coordinator.ts- Durable Object handling WebSocket connections and broadcasting
In development, Vite's dev server intercepts all WebSocket connections for HMR (Hot Module Replacement), preventing SvelteKit routes from handling WebSocket upgrades.
Solution: The WebSocket client connects directly to the backend worker (port 8787) in dev mode. This is configured via environment variables:
VITE_WS_HOST- WebSocket host (defaults tolocalhost:8787in dev,window.location.hostin production)VITE_WS_PATH- WebSocket path (defaults to/ws/connectin dev,/api/ws/connectin production)
Create a .env.dev file in the frontend/ directory with your development settings:
cd frontend
cat > .env.dev << EOF
VITE_WS_HOST=localhost:8787
VITE_WS_PATH=/ws/connect
EOFYou can override these values by setting environment variables or creating a .env.local file.
Security Note: The backend worker's /ws/connect endpoint only accepts direct connections when NODE_ENV is set to 'development' or 'test'. In production, this endpoint returns 401 Unauthorized, forcing all WebSocket connections to go through the frontend SvelteKit handler at /api/ws/connect which validates authorization.
In production, there's no Vite server - all requests go directly through the Cloudflare Worker, so WebSocket upgrades work normally on the same origin.