How to deploy TanStack Start to Cloudflare Workers with D1, R2 and better-auth
TanStack Start runs well on Cloudflare Workers. The official guides get you to "Hello world" quickly. The trouble starts when you add a database, file uploads and auth, because each of those touches the Workers runtime in a slightly different way. This post walks through a setup I use in production: one Worker that serves prerendered marketing pages as static assets and everything else from…
Deploying TanStack Start to Cloudflare Workers with D1, R2 and better-auth is straightforward with the right setup. The entire application resides within a single Worker, eliminating the need for connection strings or S3 keys. To begin, clone the free MIT-licensed starter tanstack-start-cloudflare-starter. Versions as of writing are:
- @tanstack/react-start 1.168
- @cloudflare/vite-plugin 1.40
- wrangler 4.99
- better-auth 1.6
- drizzle-orm 0.45.1
The wrangler.jsonc file configures the Worker, specifying D1, R2, and a rate limiter. Key settings include:
1. html_handling: drop-trailing-slash - This ensures prerendered pages match their canonical URLs.
2. migrations_dir: drizzle - Drizzle Kit writes SQL migrations in the ./drizzle folder, which Wrangler applies without direct interaction with D1.
3. Enable observability and set the migration directory to 'drizzle' for automatic SQL migrations.
The Vite configuration uses the Cloudflare plugin for SSR and TanStack Start for prerendering. It excludes .wrangler/ from Vite's watch list to prevent full page reloads when databases or uploads occur.
Key snippets include the Vite configuration (trimmed for brevity):
```typescript
import { cloudflare } from '@cloudflare/vite-plugin';
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
import viteReact from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
import { isPrivatePath } from './src/config/private-paths';
export default defineConfig({
resolve: { tsconfigPaths: true },
server: {
watch: {
ignored: ['**/.wrangler/**'],
},
},
plugins: [
cloudflare({
viteEnvironment: { name: 'ssr' },
}),
tanstackStart({
prerender: {
enabled: true,
crawlLinks: true,
filter: (page) => !page.path.includes('?') && !isPrivatePath(page.path),
},
}),
viteReact(),
],
});
```
This setup enables Cloudflare to run your server code in the same runtime as production, with local D1 and R2 storage. Remember to ignore .wrangler/ when using Vite's watch feature to avoid issues with database writes and uploads.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.