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 createServerFn for RPC-style server calls
  • CF Vite Plugin for real workerd during 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.