Skip to main content

TanStack Start

TanStack Start is a full-stack framework for building web applications with server-side rendering, streaming, server functions, and bundling.

Already have a TanStack Start project?

Run wrangler deploy in a project without a Wrangler configuration file and Wrangler will automatically detect TanStack Start, generate the necessary configuration, and deploy your project.

npx wrangler deploy
yarn wrangler deploy
pnpm wrangler deploy

For more information, refer to Automatic project configuration.

Create a new application

Create a TanStack Start application pre-configured for Cloudflare Workers:

npm create cloudflare@latest -- my-tanstack-start-app --framework=tanstack-start
yarn create cloudflare my-tanstack-start-app --framework=tanstack-start
pnpm create cloudflare@latest my-tanstack-start-app --framework=tanstack-start

Start a local development server to preview your project during development:

npm run dev
yarn run dev
pnpm run dev

Configure an existing application

If you have an existing TanStack Start application, configure it to run on Cloudflare Workers:

Note

Cloudflare does not support TanStack Start projects that use Rsbuild. If your application uses Rsbuild, migrate it to Vite before following these configuration steps.

  1. Install @cloudflare/vite-plugin and wrangler:

    npm i -D @cloudflare/vite-plugin wrangler
    yarn add -D @cloudflare/vite-plugin wrangler
    pnpm add -D @cloudflare/vite-plugin wrangler
    bun add -d @cloudflare/vite-plugin wrangler
  2. Add the Cloudflare plugin to your Vite configuration:

    If your Vite configuration includes another deployment adapter, such as nitro(), remove the adapter and its import before adding the Cloudflare Vite plugin.

    vite.config.js
    import { defineConfig } from "vite";
    import { tanstackStart } from "@tanstack/react-start/plugin/vite";
    import { cloudflare } from "@cloudflare/vite-plugin";
    import react from "@vitejs/plugin-react";
    
    export default defineConfig({
    	plugins: [
    		cloudflare({ viteEnvironment: { name: "ssr" } }),
    		tanstackStart(),
    		react(),
    	],
    });
    vite.config.ts
    import { defineConfig } from "vite";
    import { tanstackStart } from "@tanstack/react-start/plugin/vite";
    import { cloudflare } from "@cloudflare/vite-plugin";
    import react from "@vitejs/plugin-react";
    
    export default defineConfig({
    	plugins: [
    		cloudflare({ viteEnvironment: { name: "ssr" } }),
    		tanstackStart(),
    		react(),
    	],
    });
  3. Add a wrangler.jsonc configuration file:

    {
    	"$schema": "node_modules/wrangler/config-schema.json",
    	"name": "<YOUR_PROJECT_NAME>",
    	// Set this to today's date
    	"compatibility_date": "2026-11-02",
    	"compatibility_flags": ["nodejs_compat"],
    	"main": "@tanstack/react-start/server-entry",
    	"observability": {
    		"enabled": true,
    	},
    }
    "$schema" = "node_modules/wrangler/config-schema.json"
    name = "<YOUR_PROJECT_NAME>"
    # Set this to today's date
    compatibility_date = "2026-11-02"
    compatibility_flags = [ "nodejs_compat" ]
    main = "@tanstack/react-start/server-entry"
    
    [observability]
    enabled = true
    
  4. Update the scripts section in package.json:

    package.json
    {
    	"scripts": {
    		"dev": "vite dev",
    		"build": "vite build",
    		"preview": "vite preview",
    		"deploy": "npm run build && wrangler deploy",
    		"cf-typegen": "wrangler types"
    	}
    }

Deploy

Deploy to a *.workers.dev subdomain or a custom domain from your machine or any CI/CD system, including Workers Builds.

npm run deploy
yarn run deploy
pnpm run deploy

Note

Preview the build locally before deploying:

npm run preview
yarn run preview
pnpm run preview

Custom entrypoints

TanStack Start uses @tanstack/react-start/server-entry as your default entrypoint. Create a custom server entrypoint to add additional Workers handlers such as Queues and Cron Triggers. This is also where you can add additional exports such as Durable Objects and Workflows.

Note

If you are using TypeScript, generate Workers types before adding a custom entrypoint:

npm run cf-typegen
yarn run cf-typegen
pnpm run cf-typegen
  1. Create a custom server entrypoint file:

    src/server.js
    import handler from "@tanstack/react-start/server-entry";
    
    // Export Durable Objects as named exports
    export { MyDurableObject } from "./my-durable-object";
    
    export default {
    	fetch: handler.fetch,
    
    	// Handle Queue messages
    	async queue(batch, _env, _ctx) {
    		for (const message of batch.messages) {
    			console.log("Processing message:", message.body);
    			message.ack();
    		}
    	},
    
    	// Handle Cron Triggers
    	async scheduled(event, _env, _ctx) {
    		console.log("Cron triggered:", event.cron);
    	},
    };
    src/server.ts
    import handler from "@tanstack/react-start/server-entry";
    
    // Export Durable Objects as named exports
    export { MyDurableObject } from "./my-durable-object";
    
    export default {
        fetch: handler.fetch,
    
        // Handle Queue messages
        async queue(batch, _env, _ctx) {
            for (const message of batch.messages) {
                console.log("Processing message:", message.body);
                message.ack();
            }
        },
    
        // Handle Cron Triggers
        async scheduled(event, _env, _ctx) {
            console.log("Cron triggered:", event.cron);
        },
    } satisfies ExportedHandler<Env>;
  2. Update your Wrangler configuration to point to your custom entrypoint:

    {
    	"main": "src/server.ts",
        "upload_source_maps": true,
    }
    main = "src/server.ts"
    upload_source_maps = true
    

Test scheduled handlers locally

Test your scheduled handler locally using the /cdn-cgi/local/scheduled endpoint:

curl "http://localhost:3000/cdn-cgi/local/scheduled?cron=*+*+*+*+*"

Example: Using Workflows

Example: Using Service Bindings

Bindings

Your TanStack Start application can be fully integrated with the Cloudflare Developer Platform, in both local development and in production, by using bindings.

Access bindings by importing the env object in your server-side code:

src/routes/index.jsx
import { createFileRoute } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import { env } from "cloudflare:workers";

export const Route = createFileRoute("/")({
	loader: () => getData(),
	component: RouteComponent,
});

const getData = createServerFn().handler(() => {
	// Access bindings via env
	// For example: env.MY_KV, env.MY_BUCKET, or env.AI
});

function RouteComponent() {
	// ...
}
src/routes/index.tsx
import { createFileRoute } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import { env } from "cloudflare:workers";

export const Route = createFileRoute("/")({
	loader: () => getData(),
	component: RouteComponent,
});

const getData = createServerFn().handler(() => {
	// Access bindings via env
	// For example: env.MY_KV, env.MY_BUCKET, or env.AI
});

function RouteComponent() {
	// ...
}

Generate TypeScript types for your bindings based on your Wrangler configuration:

npm run cf-typegen
yarn run cf-typegen
pnpm run cf-typegen

With bindings, your application can be fully integrated with the Cloudflare Developer Platform, giving you access to compute, storage, AI and more.

Bindings

Access to compute, storage, AI and more.

Use R2 in a server function

Add an R2 bucket binding to your Wrangler configuration:

{
	"r2_buckets": [
		{
			"binding": "MY_BUCKET",
			"bucket_name": "<YOUR_BUCKET_NAME>",
		},
	],
}
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "<YOUR_BUCKET_NAME>"

Access the bucket in a server function:

src/routes/index.js
import { createServerFn } from "@tanstack/react-start";
import { env } from "cloudflare:workers";

const uploadFile = createServerFn({ method: "POST" })
	.validator((data) => data)
	.handler(async ({ data }) => {
		await env.MY_BUCKET.put(data.key, data.content);
		return { success: true };
	});

const getFile = createServerFn()
	.validator((key) => key)
	.handler(async ({ data: key }) => {
		const object = await env.MY_BUCKET.get(key);
		return object ? await object.text() : null;
	});
src/routes/index.ts
import { createServerFn } from "@tanstack/react-start";
import { env } from "cloudflare:workers";

const uploadFile = createServerFn({ method: "POST" })
	.validator((data: { key: string; content: string }) => data)
	.handler(async ({ data }) => {
		await env.MY_BUCKET.put(data.key, data.content);
		return { success: true };
	});

const getFile = createServerFn()
	.validator((key: string) => key)
	.handler(async ({ data: key }) => {
		const object = await env.MY_BUCKET.get(key);
		return object ? await object.text() : null;
	});

Static prerendering

Prerender your application to static HTML at build time and serve as static assets.

vite.config.js
import { defineConfig } from "vite";
import { cloudflare } from "@cloudflare/vite-plugin";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
	plugins: [
		cloudflare({ viteEnvironment: { name: "ssr" } }),
		tanstackStart({
			prerender: {
				enabled: true,
			},
		}),
		react(),
	],
});
vite.config.ts
import { defineConfig } from "vite";
import { cloudflare } from "@cloudflare/vite-plugin";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
	plugins: [
		cloudflare({ viteEnvironment: { name: "ssr" } }),
		tanstackStart({
			prerender: {
				enabled: true,
			},
		}),
		react(),
	],
});

For more options, refer to TanStack Start static prerendering.

Prerendering data sources

Caution

Prerendering runs at build time. It uses your local environment variables, secrets, and bindings storage data.

To prerender with production data, use remote bindings.

In CI environments, environment variables or secrets may not be available during the build. To make them accessible:

  • Set CLOUDFLARE_INCLUDE_PROCESS_ENV=true in your CI environment and provide the required values as environment variables.
  • If using Workers Builds, update your build settings.