Worked Example: Cloudflare Workers
Verified against
@tanstack/react-startv1.168.x — July 2026.
Source: Cloudflare’s own TanStack Start framework guide. This chapter is the applied version of that guide plus the deployment model overview’s Shape 1.
Two dev dependencies:
bun add -D @cloudflare/vite-plugin wranglerPlugin order in vite.config.ts matters — cloudflare() goes first:
import { defineConfig } from 'vite'import { cloudflare } from '@cloudflare/vite-plugin'import { tanstackStart } from '@tanstack/react-start/plugin/vite'import viteReact from '@vitejs/plugin-react'
export default defineConfig({ plugins: [ cloudflare({ viteEnvironment: { name: 'ssr' } }), tanstackStart(), viteReact(), ],})viteEnvironment: { name: 'ssr' } tells the Cloudflare plugin which Vite environment (from Vite’s environments API) is the one that needs to run under workerd semantics — that’s Start’s SSR environment, not the client one.
wrangler.jsonc
Section titled “wrangler.jsonc”{ "name": "my-start-app", "main": "@tanstack/react-start/server-entry", "compatibility_date": "2026-07-01", "compatibility_flags": ["nodejs_compat"], "observability": { "enabled": true },}main points at Start’s own server-entry module — you don’t hand-write a Worker entry point for the standard case. If you need Queues, Cron Triggers, Durable Objects, or Workflows alongside your app, swap main for your own src/server.ts and import/re-export Start’s handler from there; the Cloudflare guide covers that path for anyone going beyond a plain web app.
{ "scripts": { "dev": "vite dev", "build": "vite build", "deploy": "npm run build && wrangler deploy", "cf-typegen": "wrangler types" }}Run cf-typegen after adding any binding (KV, R2, D1, AI) — it generates the Env type wrangler.jsonc describes, so env reads are typed. For an existing project already configured this way, npx wrangler deploy alone is often enough — Wrangler auto-detects the framework and fills in defaults.
Env reads have to be per-request here — not optional
Section titled “Env reads have to be per-request here — not optional”This is the sharpest edge of the Workers runtime specifically, so it’s worth stating plainly rather than just linking past it: a Worker isolate is reused across requests. Anything read once at module scope and cached in a variable stays cached for every request that isolate handles afterward, potentially mixing state across unrelated requests. Caching and env vars covers why this matters in general; on Cloudflare specifically, it’s not a theoretical footgun — it’s the default behavior if you get it wrong.
// cloudflare:workers — the binding for env access inside server codeimport { env } from 'cloudflare:workers'
// Wrong — read once, reused (and stale) for every subsequent request on this isolateconst apiKey = env.THIRD_PARTY_API_KEYexport const callThirdParty = createServerFn().handler(async () => { return fetch('https://api.example.com', { headers: { Authorization: apiKey } })})
// Right — read inside the handler, fresh every callexport const callThirdParty = createServerFn().handler(async () => { const apiKey = env.THIRD_PARTY_API_KEY return fetch('https://api.example.com', { headers: { Authorization: apiKey } })})In practice this rarely bites you with static secrets (env.THIRD_PARTY_API_KEY is the same value every request anyway) — it bites you with anything derived from the request or from per-tenant config that you’re tempted to memoize “for performance.” Don’t memoize it at module scope on Workers.
Prerendering, if you want it
Section titled “Prerendering, if you want it”tanstackStart({ prerender: { enabled: true } }) prerenders eligible routes at build time (requires @tanstack/react-start v1.138.0+). It uses your local env during the build; if a prerendered route needs production-only bindings, enable remote bindings so the build can reach them.