123456789┌──────────────────────────────────────────────┐ ┌─────────────────────────────────────────────┐ │ Main Worker (Spiceflow + Vite) │ │ Dedicated Worker (Spiceflow only) │ │ │ │ │ │ / ──▶ React pages │ │ /api/og ──▶ renderOgImage() │ │ /dashboard ──▶ React pages │ │ (WASM, heavy deps) │ │ │ │ │ │ /api/og ────────────────────────────────────┤──────▶├ No Vite plugin needed │ │ env.OG_WORKER.fetch(request) │ │ │ └──────────────────────────────────────────────┘ └─────────────────────────────────────────────┘
services entry in the main Worker's wrangler.jsonc. Each environment needs its own binding pointing to the correct worker name for that environment.12345678910111213// main worker wrangler.jsonc { "services": [ { "binding": "OG_WORKER", "service": "my-og-worker" } ], "env": { "preview": { "services": [ { "binding": "OG_WORKER", "service": "my-og-worker-preview" } ] } } }
wrangler types after adding the binding so env.OG_WORKER is typed.12345678910111213// main worker src/main.tsx import { Spiceflow } from 'spiceflow' import { env } from 'cloudflare:workers' export const app = new Spiceflow() .page('/', async () => <div>Home</div>) .get('/api/og', ({ request }) => env.OG_WORKER.fetch(request)) export default { fetch(request: Request) { return app.handle(request) }, }
import() inside the handler to keep the heavy dependency out of the cold-start path; the module is only loaded when the route is actually called.12345678910111213141516171819202122232425// dedicated worker src/main.tsx import { Spiceflow } from 'spiceflow' import { z } from 'zod' const querySchema = z.object({ title: z.string(), description: z.string().optional(), }) export const app = new Spiceflow().route({ method: 'GET', path: '/api/og', query: querySchema, async handler({ query }) { // Dynamic import keeps WASM out of the cold-start path const { renderOgImage } = await import('./og.tsx') return renderOgImage(query) }, }) export default { fetch(request: Request) { return app.handle(request) }, }
wrangler.jsonc with preview and production environment configs. The main Worker's service binding resolves at request time, so the dedicated Worker must be live before the main Worker can call it.12345# Deploy dedicated worker first cd my-og-worker && wrangler deploy # Then deploy main worker cd my-main-worker && wrangler deploy