DEV Community

Isaac FEI
Isaac FEI

Posted on

Deploying a TanStack Start App with Neon Postgres and Cloudflare Workers

  • URL: https://isaacfei.com/posts/deploy-tanstack-start-neon-cloudflare
  • Date: 2026-02-23
  • Tags: TanStack Start, Cloudflare Workers, Neon, Drizzle ORM, Deployment
  • Description: A step-by-step guide to deploying a full-stack TanStack Start application with Neon serverless Postgres (via Drizzle ORM) to Cloudflare Workers, using GitHub integration for CI/CD.

This guide walks through deploying a full-stack TanStack Start application to Cloudflare Workers with Neon as the serverless Postgres database, using Drizzle ORM for type-safe database access. We'll set up the database, configure the project for Cloudflare, and deploy via Cloudflare's GitHub integration so every push triggers a new deployment automatically.

The Stack

Layer Technology
Framework TanStack Start (React, full-stack with SSR)
Database Neon (serverless Postgres)
ORM Drizzle ORM (with neon-http driver)
Hosting Cloudflare Workers
CI/CD Cloudflare GitHub integration

Why this combination? TanStack Start runs on Cloudflare Workers as an official deployment target. Neon provides serverless Postgres that's accessible over HTTP — perfect for edge runtimes where traditional TCP-based Postgres connections aren't available. Drizzle ORM ties it together with type-safe queries and zero-overhead migrations.

Prerequisites

  • A Neon account (free tier works)
  • A Cloudflare account (free Workers plan works)
  • A GitHub repository with your TanStack Start project
  • Node.js 18+ and pnpm installed

Step 1: Set Up Neon Database

Create a Neon Project

Head to the Neon Console and create a new project (or use an existing one).

Get the Connection String

In your project dashboard, click the Connect button. This opens the connection details modal showing your connection string.
Neon

Copy the connection string. It looks like this:

postgresql://neondb_owner:abc123@ep-cool-darkness-123456-pooler.us-east-1.aws.neon.tech/neondb?sslmode=require
Enter fullscreen mode Exit fullscreen mode

Create a .env.local file in your project root and add the connection string:

DATABASE_URL="postgresql://neondb_owner:abc123@ep-cool-darkness-123456-pooler.us-east-1.aws.neon.tech/neondb?sslmode=require"
Enter fullscreen mode Exit fullscreen mode

This file is for local development only. We'll set this as a secret in Cloudflare later for production.

Step 2: Set Up Drizzle ORM with Neon

Install Dependencies

pnpm add drizzle-orm @neondatabase/serverless
pnpm add -D drizzle-kit dotenv tsx
Enter fullscreen mode Exit fullscreen mode
  • drizzle-orm — the ORM itself
  • @neondatabase/serverless — Neon's serverless driver for HTTP-based Postgres access (works in edge runtimes)
  • drizzle-kit — CLI for migrations and schema management
  • dotenv — loads .env.local for local scripts
  • tsx — runs TypeScript files directly (for seed scripts, etc.)

Define Your Schema

Create your schema file. Here's an example with a documents table:

// src/server/db/schema/documents.ts
import { pgTable, text, uuid, timestamp } from "drizzle-orm/pg-core";
import { sql } from "drizzle-orm";

export const documentsTable = pgTable("documents", {
  id: uuid("id").primaryKey().defaultRandom(),
  title: text("title"),
  content: text("content"),
  checksum: text("checksum"),
  createdAt: timestamp("created_at", { withTimezone: true })
    .notNull()
    .defaultNow(),
  updatedAt: timestamp("updated_at", { withTimezone: true })
    .notNull()
    .defaultNow()
    .$onUpdate(() => sql`now()`),
});
Enter fullscreen mode Exit fullscreen mode

Export it from an index file:

// src/server/db/schema/index.ts
export * from "./documents";
Enter fullscreen mode Exit fullscreen mode

Create the Database Connection

Drizzle provides a dedicated neon-http driver integration. This uses HTTP to talk to Neon — no TCP sockets needed, which is exactly what Cloudflare Workers requires.

// src/server/db/db.ts
import { drizzle } from "drizzle-orm/neon-http";

export const db = drizzle(process.env.DATABASE_URL!);
Enter fullscreen mode Exit fullscreen mode

That's it. Drizzle creates a Neon HTTP client under the hood using the connection string. You can now use db in your server functions.

Configure Drizzle Kit

Create a drizzle.config.ts in the project root:

// drizzle.config.ts
import { config } from "dotenv";
import { defineConfig } from "drizzle-kit";

config({ path: ".env.local" });

export default defineConfig({
  out: "./drizzle",
  schema: "./src/server/db/schema",
  dialect: "postgresql",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});
Enter fullscreen mode Exit fullscreen mode

Run Migrations

Add these scripts to your package.json:

{
  "scripts": {
    "db:generate": "drizzle-kit generate",
    "db:migrate": "drizzle-kit migrate"
  }
}
Enter fullscreen mode Exit fullscreen mode

Generate and apply migrations:

pnpm db:generate
pnpm db:migrate
Enter fullscreen mode Exit fullscreen mode

db:generate creates SQL migration files in the ./drizzle folder based on your schema. db:migrate applies them to your Neon database. You can run db:generate whenever you change your schema and db:migrate to apply the changes.

Step 3: Configure TanStack Start for Cloudflare Workers

Cloudflare Workers is an official deployment target for TanStack Start. The setup requires three things: the Cloudflare Vite plugin, a Wrangler config, and updated scripts.

Install Cloudflare Dependencies

pnpm add -D @cloudflare/vite-plugin wrangler
Enter fullscreen mode Exit fullscreen mode

Update vite.config.ts

Add the Cloudflare plugin to your Vite config. It must come before the TanStack Start plugin:

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

export default defineConfig({
  plugins: [
    cloudflare({ viteEnvironment: { name: "ssr" } }),
    tanstackStart(),
    viteReact(),
  ],
});
Enter fullscreen mode Exit fullscreen mode

The viteEnvironment: { name: "ssr" } tells the Cloudflare plugin to handle the SSR environment, which is where TanStack Start's server code runs.

Add wrangler.jsonc

Create a wrangler.jsonc file in the project root:

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "your-project-name",
  "compatibility_date": "2025-09-02",
  "compatibility_flags": ["nodejs_compat"],
  "main": "@tanstack/react-start/server-entry"
}
Enter fullscreen mode Exit fullscreen mode

A few notes:

  • Change "name" to your project name — this becomes your *.workers.dev subdomain
  • nodejs_compat enables Node.js APIs in Workers (required for many npm packages)
  • main points to TanStack Start's server entry point

Update package.json Scripts

{
  "scripts": {
    "dev": "vite dev",
    "build": "vite build && tsc --noEmit",
    "preview": "vite preview",
    "deploy": "pnpm run build && wrangler deploy"
  }
}
Enter fullscreen mode Exit fullscreen mode

The key changes from a standard TanStack Start setup:

  • Remove the "start": "node .output/server/index.mjs" script (Cloudflare Workers doesn't use Node)
  • Add "preview" for local testing with Wrangler's Workers runtime
  • Add "deploy" for manual deployment via CLI (optional if using GitHub integration)

Step 4: Deploy to Cloudflare via GitHub Integration

Instead of deploying via wrangler deploy from the command line, we'll connect the GitHub repository directly to Cloudflare. This gives you automatic deployments on every push — no CI configuration needed.

Connect Your Repository

  1. Go to the Cloudflare dashboard and navigate to Workers & Pages.
  2. Click Create, then select the Import a repository tab (or similar option to connect Git).
  3. Connect your GitHub account if you haven't already. Authorize Cloudflare to access your repositories.
  4. Select the repository containing your TanStack Start project.

Configure Build Settings

After selecting your repository, configure the build settings:

Setting Value
Production branch main (or your default branch)
Build command pnpm run build
Deploy command npx wrangler deploy

Cloudflare will use these settings to build and deploy your app whenever you push to the production branch.

Set Environment Variables

Your app needs the DATABASE_URL to connect to Neon. Do not commit this to your repository.

  1. After creating the Worker (or in the Worker's Settings page), go to SettingsVariables and Secrets.
    Cloudflare Worker Settings page, Variables and Secrets section

  2. Click Add, choose type Secret, and set:

  • Variable name: DATABASE_URL
  • Value: your Neon connection string Adding the DATABASE\_URL secret
  1. Click Save (or Deploy if prompted — deploying saves the variables and triggers a new deployment).

Using Secret (instead of plain text) ensures the value is encrypted and never shown in the dashboard after saving.

Trigger a Deployment

Once the GitHub integration is set up:

  • Automatic: Push to your production branch. Cloudflare detects the push, runs the build command, and deploys.
  • Manual: In the Cloudflare dashboard, go to your Worker and click DeploymentsDeploy to trigger a build from the latest commit.

Your app is now live at https://your-project-name.your-subdomain.workers.dev. You can also add a custom domain in the Worker settings.

Putting It All Together

Here's the complete file structure for the deployment-related configuration:

.
├── drizzle/                  # generated migration files
├── src/
│   └── server/
│       └── db/
│           ├── db.ts         # Drizzle + Neon connection
│           └── schema/
│               └── index.ts  # table definitions
├── .env.local                # DATABASE_URL (local dev only, git-ignored)
├── drizzle.config.ts         # Drizzle Kit config
├── vite.config.ts            # Vite + Cloudflare + TanStack Start plugins
├── wrangler.jsonc            # Cloudflare Workers config
└── package.json              # build/deploy scripts
Enter fullscreen mode Exit fullscreen mode

The deployment flow looks like this:

flowchart TB
    Push["Git Push"] --> CF["Cloudflare<br/>Builds Worker"]
    CF --> Deploy["Deploy to<br/>Workers Edge"]
    Deploy --> App["Your App<br/>(SSR on Workers)"]
    App -->|"HTTP query"| Neon["Neon Postgres<br/>(serverless)"]
Enter fullscreen mode Exit fullscreen mode

And the development workflow:

flowchart TB
    subgraph Local ["Local Development"]
        Dev["pnpm dev"] --> Vite["Vite Dev Server<br/>(port 3000)"]
        Vite --> App["TanStack Start App"]
        App -->|"DATABASE_URL<br/>from .env.local"| NeonDB["Neon Postgres"]
    end

    subgraph Schema ["Schema Changes"]
        Edit["Edit schema/*.ts"] --> Gen["pnpm db:generate"]
        Gen --> Migrate["pnpm db:migrate"]
        Migrate --> NeonDB
    end

    subgraph Deploy ["Deployment"]
        GitPush["git push"] --> CFBuild["Cloudflare Build"]
        CFBuild --> Worker["Cloudflare Worker"]
        Worker -->|"DATABASE_URL<br/>from Secrets"| NeonDB
    end
Enter fullscreen mode Exit fullscreen mode

Common Issues

Error: connect ECONNREFUSED or TCP connection errors — Make sure you're using the neon-http driver (drizzle-orm/neon-http), not the standard pg driver. Cloudflare Workers doesn't support TCP sockets; the Neon HTTP driver uses fetch() under the hood.

Missing environment variable DATABASE_URL — Check that you've added the variable in Cloudflare Worker settings as a Secret. For local development, make sure .env.local exists and contains the variable.

Build fails with wrangler not found — Ensure wrangler is in devDependencies, not dependencies. The Cloudflare build environment installs dev dependencies by default.

nodejs_compat errors — Make sure "compatibility_flags": ["nodejs_compat"] is in your wrangler.jsonc. Some npm packages (including Drizzle and crypto libraries) need Node.js APIs that are only available with this flag.

Summary

The deployment setup is straightforward once you know the pieces:

  1. Neon provides a serverless Postgres database accessible over HTTP — create a project, grab the connection string
  2. Drizzle ORM with the neon-http driver gives you type-safe database access that works in edge runtimes
  3. Cloudflare Workers runs your TanStack Start app at the edge — add the Vite plugin and Wrangler config
  4. GitHub integration in Cloudflare gives you automatic CI/CD — connect your repo, set build commands, add secrets, and every push deploys

The total configuration is minimal: one Vite plugin, one Wrangler config file, one Drizzle config, and a 3-line database connection module. The rest is handled by the platforms.

Top comments (0)