Create FasterCreate Faster

Cloudflare Workers

Cloudflare Workers deployment platform — stack-aware config for Hono, Next.js (OpenNext) and TanStack Start (Vite plugin).

Supported frameworks

→ Cloudflare Workers Documentation

What create-faster adds

Cloudflare Workers is a project-level deployment platform. Generation is stack-aware per app: a Hono app gets a direct Workers config, a Next.js app gets an OpenNext config, and a TanStack Start app gets the Cloudflare Vite plugin. Selecting it once with --deployment cloudflare configures every app according to its stack.

Hono apps

Hono already exports a Workers-compatible fetch handler (export default app), so no adapter is needed — local dev keeps running on Bun while Wrangler handles preview and deploy.

wrangler.jsonc
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-api",
  "main": "src/index.ts",
  "compatibility_date": "2026-06-12",
  "compatibility_flags": ["nodejs_compat"]
}

Scripts:

  • deploy - Deploy the Worker (wrangler deploy)
  • preview - Run the Worker locally on the Workers runtime (wrangler dev)
  • cf-typegen - Generate binding types (wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts)

Next.js apps

Next.js apps deploy through the OpenNext Cloudflare adapter. next.config.ts calls initOpenNextCloudflareForDev() so Cloudflare bindings are available during next dev, and the build emits the Worker output under .open-next/.

wrangler.jsonc
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "web",
  "main": ".open-next/worker.js",
  "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
  "assets": {
    "directory": ".open-next/assets",
    "binding": "ASSETS"
  }
}
open-next.config.ts
import { defineCloudflareConfig } from '@opennextjs/cloudflare';

export default defineCloudflareConfig({});

Scripts:

  • build:cf - Generate binding types, Next.js build, then the OpenNext adapter build (wrangler types … && next build && opennextjs-cloudflare build). Types are generated first so the binding globals (e.g. Hyperdrive, D1Database) typecheck during next build.
  • preview - Local Wrangler preview of the built output (opennextjs-cloudflare preview)
  • deploy - Deploy the built output (opennextjs-cloudflare deploy)
  • cf:typegen - Generate binding types (wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts)

TanStack Start apps

TanStack Start runs on Workers through @cloudflare/vite-plugin, without Nitro: vite.config.ts registers the plugin in place of nitro(), and vite dev and vite preview run the app in workerd with the bindings declared in wrangler.jsonc. The Nitro start script and the .env.start file are not generated. The Vite config passes vars: { NODE_ENV } to the plugin, so the built wrangler.json sets NODE_ENV=production in vite preview and wrangler deploy while vite dev runs with development.

wrangler.jsonc
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "web",
  "main": "src/server.ts",
  "compatibility_date": "2026-06-12",
  "compatibility_flags": ["nodejs_compat"]
}

src/server.ts is the Worker entry: it exports Start's handler (@tanstack/react-start/server-entry). Libraries wrap it, for example the evlog module wraps fetch with withEvlog.

Scripts:

  • build - Build the client assets and the Worker (vite build)
  • preview - Serve the production build in workerd (vite preview). Check the build here before deploying: vite dev hides errors that only appear once the Worker is built, such as I/O or randomness at module level.
  • deploy - Build then deploy (vite build && wrangler deploy)
  • cf-typegen - Generate binding types (wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts). Run it after install and after each change to wrangler.jsonc: tsc reads cloudflare-env.d.ts for the cloudflare:workers module.

Bindings are read with import { env } from 'cloudflare:workers'. With --database d1, src/lib/server.ts builds the Drizzle db once at module scope from env.DB, which is safe for D1. With Postgres or MySQL, build a client per request with await createDb(env.HYPERDRIVE) (see below).

Postgres / MySQL via Hyperdrive

A Worker cannot open a raw TCP connection to a Postgres or MySQL server, and forbids reusing a connection across requests. When a Next.js or TanStack Start app uses --database postgres (or mysql) with --deployment cloudflare, the database is routed through a Cloudflare Hyperdrive binding (a pooling proxy), and the db package exports a per-request createDb(hyperdrive) factory instead of a module-level singleton.

wrangler.jsonc
"hyperdrive": [
  {
    "binding": "HYPERDRIVE",
    "id": "REPLACE_WITH_HYPERDRIVE_ID",
    "localConnectionString": "postgresql://postgres:password@localhost:5432/postgres-my-app"
  }
]
  • Setup: wrangler hyperdrive create <name> --connection-string="...", then paste the id into wrangler.jsonc. localConnectionString is used by local runs only (wrangler dev, vite dev, vite preview) and matches your local DATABASE_URL.
  • Migrations run on Node via drizzle-kit against DATABASE_URL — unchanged by Hyperdrive.
  • better-auth / tRPC over a Cloudflare binding database (per-request consumers) is not wired yet, so those combinations are disabled with a reason on Cloudflare (tracked in #153).

Shared behavior

The Worker name is the app name in turborepo mode and the project name in single mode.

Modified files:

  • .gitignore - Ignores .wrangler/ and cloudflare-env.d.ts; for Next.js apps it also ignores .open-next/ and .prod.env.

Local secrets:

Wrangler 4.x and the Vite plugin read local secrets and variables from .env (already gitignored). Put your local values there and run the preview script. Production secrets are managed with wrangler secret put <KEY>. We standardize on .env rather than .dev.vars because Wrangler ignores .env whenever a .dev.vars file is present, which would silently shadow it.

Turborepo mode: wrangler.jsonc (and open-next.config.ts for Next.js apps) is placed in apps/<name>/, and the Worker is named after the app. Run the scripts from the app directory.

On this page