TanStack Start on Cloudflare Workers
TanStack Start is a full-stack React framework built on TanStack Router. It uses the CF Vite Plugin for workerd dev parity. Its differentiator over React Router v7 is fully type-safe routing, where route params, search params, loaders, and actions are all inferred from your route definitions.
Create a New Project
npm create cloudflare@latest my-tanstack-app -- --framework=tanstack-start
Project Structure
my-tanstack-app/
├── app/
│ ├── routes/
│ │ ├── __root.tsx # Root layout
│ │ └── index.tsx # Index route
│ ├── client.tsx # Client entry
│ ├── router.tsx # Router config
│ └── ssr.tsx # SSR entry
├── workers/
│ └── app.ts # Worker entry point
├── app.config.ts # TanStack Start config
├── vite.config.ts # Vite + CF plugin
└── wrangler.jsonc # Cloudflare config
Vite Config
// vite.config.ts
import { tanstackStart } from "@tanstack/start/vite";
import { cloudflare } from "@cloudflare/vite-plugin";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
cloudflare({ viteEnvironment: { name: "ssr" } }),
tanstackStart(),
],
});
Worker Entry Point
// workers/app.ts
import { createStartHandler, defaultStreamHandler } from "@tanstack/start/server";
import { createRouter } from "../app/router";
const handler = createStartHandler({ createRouter });
export default {
async fetch(request: Request, env: Env) {
return handler({ request, env })(defaultStreamHandler);
},
} satisfies ExportedHandler<Env>;
Route with Type-Safe Loading
// app/routes/index.tsx
import { createFileRoute } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/start";
const getVisits = createServerFn({ method: "GET" }).handler(async () => {
// Server function runs in workerd
// Access bindings through the request context
return { visits: 42 };
});
export const Route = createFileRoute("/")({
loader: () => getVisits(),
component: Home,
});
function Home() {
const { visits } = Route.useLoaderData();
return <h1>Visits: {visits}</h1>;
}
Server Functions
TanStack Start uses “server functions” instead of loaders/actions. They’re RPC-style functions that run on the server.
import { createServerFn } from "@tanstack/start";
const incrementCounter = createServerFn({ method: "POST" })
.validator((data: { amount: number }) => data)
.handler(async ({ data }) => {
// Runs on the server (in workerd on Cloudflare)
console.log(`Incrementing by ${data.amount}`);
return { success: true };
});
// Call from a component
function Counter() {
const handleClick = async () => {
await incrementCounter({ data: { amount: 1 } });
};
return <button onClick={handleClick}>Increment</button>;
}
Dev and Deploy
# Development (runs in workerd via CF Vite Plugin)
npm run dev
# Deploy
npx wrangler deploy
Key Points
- Type-safe routing: params, search params, loader data all inferred
- Server functions via
createServerFnfor RPC-style server calls - CF Vite Plugin for real
workerdduring dev - Worker entry point allows colocating Durable Objects, Queues, etc.
- Built on TanStack Router, which has a mature ecosystem (query integration, devtools)
Gotcha: TanStack Start is newer than React Router v7 and has a smaller community. The Cloudflare integration works but has fewer examples and less battle-testing in production.
Tip: If you’re already using TanStack Router for client-side routing and want to add SSR on Cloudflare, TanStack Start is the natural upgrade path.