Summer sale!-$100 off
home

Deploy TurboStarter to Cloudflare Workers - complete guide

ยท15 min read

Learn how to deploy your TurboStarter Next.js SaaS to Cloudflare Workers with OpenNext, Hyperdrive, R2, Wrangler, and production-ready environment setup.

If you want to deploy TurboStarter to Cloudflare Workers, you do not need to rewrite your Next.js SaaS from scratch. TurboStarter ships a Cloudflare generator that wires OpenNext for Cloudflare, Wrangler, Hyperdrive, and R2 into a production-shaped deployment path.

Short answer: run pnpm turbo gen cloudflare, create Hyperdrive and an R2 cache bucket, configure build-time and runtime environment variables, preview with pnpm --filter web cf:preview, then deploy with pnpm --filter web cf:deploy. Your app runs on Cloudflare Workers close to users worldwide while Postgres stays on your existing provider through Hyperdrive.

This guide walks through the full TurboStarter Cloudflare deployment flow, explains which Cloudflare services matter for a SaaS app, and links to the official docs when you want to go deeper.

Why deploy a Next.js SaaS to Cloudflare Workers?

Cloudflare Workers run your application on Cloudflare's global edge network instead of a single regional Node.js server. For a TurboStarter SaaS, that can mean lower latency for auth flows, dashboards, marketing pages, and API routes โ€” especially if your users are spread across regions.

Compared with a traditional VPS or container host, Cloudflare is attractive when you want:

  • Global edge execution via Cloudflare Workers
  • Managed database connection pooling via Hyperdrive
  • Low-cost object storage via R2 without S3 egress fees
  • Incremental cache storage for OpenNext through an R2 binding
  • Simple CLI workflows with Wrangler
  • Optional add-ons like Turnstile, Queues, and Workers KV as your product grows

TurboStarter still supports Vercel for the easiest Next.js hosting experience and Docker when you want full Node.js runtime control. Cloudflare is the best fit when you want edge deployment without managing servers yourself.

Hosting optionBest forTradeoff
VercelFastest default Next.js deploymentLess control over edge/database architecture
Cloudflare WorkersGlobal edge + R2 + HyperdriveWorker size limits and Workers runtime constraints
DockerFull Node.js control, self-hostingYou manage infrastructure and scaling
Railway / RenderManaged containers with Postgres nearbyNot edge-native

How TurboStarter deploys to Cloudflare

TurboStarter does not use Next.js on Pages as the primary path. It uses OpenNext for Cloudflare to transform the Next.js build output into a Worker-compatible package that runs in the Workers runtime with nodejs_compat enabled.

The default production stack looks like this:

LayerServiceRole in TurboStarter
App runtimeCloudflare WorkersRuns the transformed Next.js app at the edge
Build adapterOpenNext for CloudflareConverts next build output into Worker code
CLIWranglerPreviews, configures secrets, and deploys the Worker
Database accessHyperdrivePools Postgres connections inside Cloudflare's network
Incremental cacheR2Stores OpenNext incremental cache via NEXT_INC_CACHE_R2_BUCKET
App databaseYour Postgres providerRemains your source of truth; Hyperdrive connects Workers to it

That architecture matters because TurboStarter is a full SaaS app โ€” auth, billing, organizations, admin, emails, and storage โ€” not a static marketing site. You need reliable Postgres access, runtime secrets, and a deployment flow that matches production behavior before you go live.

Prerequisites

Before deploying TurboStarter to Cloudflare, make sure you have:

Cloudflare Workers also have script size limits. Wrangler prints the compressed upload size during deployment, so check that output if the upload is rejected.

Cloudflare Workers dashboard

Step 1: Generate the Cloudflare setup

The fastest path is TurboStarter's Cloudflare generator. Run it from the repository root:

pnpm turbo gen cloudflare

The generator asks for:

  • Worker name โ€” the Cloudflare Worker name, usually your product slug
  • R2 bucket name โ€” the bucket OpenNext uses for incremental cache
  • Hyperdrive config id โ€” paste this after you create Hyperdrive in the next step
  • Local database URL โ€” used by Wrangler preview
  • App URL โ€” used by local Cloudflare preview
  • Wrangler compatibility date โ€” keep the default unless you have a reason to pin it

It creates and updates the files needed by the web app:

  • apps/web/wrangler.jsonc
  • apps/web/open-next.config.ts
  • apps/web/scripts/cf-build.mjs
  • apps/web/.dev.vars.example
  • apps/web/next.config.ts
  • apps/web/package.json
  • apps/web/middleware.ts
  • packages/db/src/server.ts
  • turbo.json

It also adds scripts like cf:build, cf:preview, cf:deploy, cf:upload, and cf:typegen to the web package.

Prefer doing it manually?

You can add the same files yourself. Use the generated setup as the source of truth: wrangler.jsonc points to .open-next/worker.js, enables nodejs_compat, binds ASSETS, IMAGES, NEXT_INC_CACHE_R2_BUCKET, and HYPERDRIVE, while open-next.config.ts enables the R2 incremental cache override.

For the full reference, see the Cloudflare deployment docs.

Step 2: Create Hyperdrive for Postgres

Cloudflare Workers run globally, while most Postgres databases live in one region. Without connection pooling, serverless-style deployments can exhaust database connections quickly โ€” especially in a SaaS app with auth sessions, billing webhooks, and dashboard queries.

Hyperdrive solves that by keeping pooled connections inside Cloudflare's network and exposing a Worker binding called HYPERDRIVE.

Create a Hyperdrive configuration with your production database URL:

pnpm --filter web exec wrangler hyperdrive create <name> --connection-string="$DATABASE_URL"

Copy the returned id into apps/web/wrangler.jsonc:

apps/web/wrangler.jsonc
{
  "hyperdrive": [
    {
      "binding": "HYPERDRIVE",
      "id": "<your-hyperdrive-id>",
      "localConnectionString": "postgresql://turbostarter:turbostarter@localhost:5432/core"
    }
  ]
}

Keep localConnectionString pointed at your local database. Wrangler uses it when you run cf:preview.

Hyperdrive connection pooling

Hyperdrive is one of the most important pieces of a TurboStarter Cloudflare deployment because TurboStarter uses Drizzle ORM against PostgreSQL for auth, billing, organizations, and app data. Treat database connectivity as a first-class launch requirement, not an afterthought.

Step 3: Create the R2 cache bucket

The generated open-next.config.ts configures OpenNext to use R2 for incremental cache. Create the bucket with the same name you entered in the generator:

pnpm --filter web exec wrangler r2 bucket create <bucket-name>

Then verify the binding in apps/web/wrangler.jsonc:

apps/web/wrangler.jsonc
{
  "r2_buckets": [
    {
      "binding": "NEXT_INC_CACHE_R2_BUCKET",
      "bucket_name": "<bucket-name>"
    }
  ]
}

If you are not using ISR or cached server data yet, you can still keep this bucket configured. It gives your app the right production shape before you need it.

Cache bucket vs user uploads

This R2 bucket is for OpenNext's incremental cache, not for user files. If your TurboStarter app stores uploads, exports, avatars, or generated assets, create a separate Cloudflare R2 bucket and wire it through the storage package. Keeping cache and user files separate makes permissions, lifecycle rules, and cleanup much easier.

Step 4: Configure environment variables

Cloudflare needs two kinds of variables for a TurboStarter SaaS deployment:

  • Build-time variables โ€” available when cf:build runs, especially NEXT_PUBLIC_* values and anything used by static generation
  • Runtime variables and secrets โ€” available to the deployed Worker during requests

For local preview, copy the generated example file:

cp apps/web/.dev.vars.example apps/web/.dev.vars

Keep your normal local values in .env.local and apps/web/.env.local as described in environment variables. Use .dev.vars only for values Wrangler needs during Worker preview, such as DATABASE_URL and URL.

For production, add the same variables you use for other deployments:

Production variables
URL="https://example.com"
BETTER_AUTH_URL="https://example.com"
NEXT_PUBLIC_URL="https://example.com"
DATABASE_URL="postgresql://..."
BETTER_AUTH_SECRET="..."

You can set them in the Cloudflare dashboard or with Wrangler:

pnpm --filter web exec wrangler secret put DATABASE_URL
pnpm --filter web exec wrangler secret put BETTER_AUTH_SECRET

The generated wrangler.jsonc includes keep_vars: true, so deployments will not remove variables you manage in the Cloudflare dashboard.

Use separate staging and production values

If you deploy multiple environments, keep the same variable names and change only the values. The multiple environments recipe shows the recommended structure.

Cloudflare secrets and variables

For a TurboStarter SaaS, pay special attention to:

  • URL, NEXT_PUBLIC_URL, and BETTER_AUTH_URL โ€” must match your production domain
  • DATABASE_URL โ€” production Postgres connection string used by Hyperdrive
  • BETTER_AUTH_SECRET โ€” required for session security
  • Billing provider secrets โ€” Stripe, Lemon Squeezy, or Polar depending on your setup
  • Email provider credentials โ€” if magic links or transactional email are enabled
  • Storage credentials โ€” if you use S3-compatible storage or R2 for user files

Review the full production deployment checklist before going live.

Step 5: Generate Cloudflare binding types

After wrangler.jsonc has a real Hyperdrive id and R2 bucket name, generate Worker binding types:

pnpm --filter web cf:typegen

This creates apps/web/cloudflare-env.d.ts. The generator also adds that file to the web app's TypeScript config so bindings like HYPERDRIVE and NEXT_INC_CACHE_R2_BUCKET are typed correctly.

Step 6: Preview locally in the Workers runtime

pnpm dev is still the fastest command for daily development. For Cloudflare deployment validation, preview the app in the same Workers runtime it will use in production:

pnpm --filter web cf:preview

Test the paths that depend on external services:

  • sign in and sign out
  • organization creation
  • database reads and writes
  • billing checkout and webhooks
  • email sending
  • file uploads, if your app uses storage

Preview before every first deploy

cf:preview is the production-shape check that catches Worker, Hyperdrive, and environment issues before your users do. Run it after changing wrangler.jsonc, secrets, Hyperdrive, or database-related code.

Step 7: Deploy to Cloudflare

When preview looks good, deploy the Worker from your local machine:

pnpm --filter web cf:deploy

This runs the custom Cloudflare build script, transforms the Next.js output with OpenNext, uploads the Worker, and deploys it to Cloudflare.

If you want to upload a version without immediately routing traffic to it, use:

pnpm --filter web cf:upload

After deployment, open the Worker URL and update your production URL, NEXT_PUBLIC_URL, BETTER_AUTH_URL, OAuth callbacks, and billing webhooks to use the final domain.

Cloudflare deployment success

Step 8: Add a custom domain

In the Cloudflare dashboard, open your Worker and add a custom domain or route for your production host.

After the domain is active:

  • update URL, NEXT_PUBLIC_URL, and BETTER_AUTH_URL
  • update OAuth redirect URLs in each provider
  • update billing webhook URLs to /api/billing/webhook
  • redeploy with pnpm --filter web cf:deploy

Cloudflare custom domain setup

If your domain is already on Cloudflare, you also get access to DNS, TLS, caching, and security controls in one place โ€” useful for a production SaaS launch.

Cloudflare services worth knowing for TurboStarter

You do not need every Cloudflare product on day one. The default TurboStarter Cloudflare setup uses Workers, Hyperdrive, R2, and Wrangler. As your SaaS grows, these services become useful add-ons:

ServiceWhat it doesGood TurboStarter use case
WorkersRuns your app at the edgeHost the Next.js SaaS itself
HyperdrivePools Postgres connections for WorkersConnect Workers to your main TurboStarter database
R2S3-compatible object storageIncremental cache, uploads, exports, generated files
ImagesOptimize and serve images from the edgeAvatars, marketing assets, user-generated media
Workers KVLow-latency global key-value storageFeature flags, public config, cached lookup tables
QueuesAsync background processingEmail fan-out, imports, retries, webhook processing
Durable ObjectsStrongly consistent stateful computeLive collaboration, presence, chat, rate limits
D1Serverless SQLite at the edgeSmall edge-native features, not your main SaaS database
TurnstileBot protection without traditional CAPTCHAsSign-up, contact, invite, and waitlist forms
ZarazThird-party tool loading through CloudflareAnalytics and marketing tags with less client bloat

Start small

Keep Postgres as the main TurboStarter database unless you intentionally redesign that layer. Services like KV, D1, Durable Objects, and Queues are best added for specific workloads, not as replacements for the default app database on day one.

When to add R2 for user uploads

TurboStarter's storage layer supports S3-compatible providers, including Cloudflare R2. That is separate from the OpenNext cache bucket. Use R2 for user-facing files when you want:

  • no S3 egress fees on large download volumes
  • storage close to your Cloudflare-hosted app
  • simple bucket-based permissions for uploads and exports

See the storage overview for wiring R2 into your app.

When to add Turnstile

If your SaaS has public sign-up, invite acceptance, contact forms, or waitlists, Turnstile is a strong fit. It reduces bot traffic without the UX pain of traditional CAPTCHAs โ€” especially helpful right after launch when marketing traffic spikes.

When to add Queues

TurboStarter already supports background work patterns, but Queues become useful when you need durable async processing at the edge: sending emails in bulk, importing data, retrying failed webhooks, or decoupling slow tasks from user-facing requests.

Useful TurboStarter Cloudflare commands

# Build the OpenNext Worker output
pnpm --filter web cf:build

# Preview locally in the Workers runtime
pnpm --filter web cf:preview

# Deploy to Cloudflare
pnpm --filter web cf:deploy

# Upload a new Worker version without deploying traffic immediately
pnpm --filter web cf:upload

# Regenerate Cloudflare binding types
pnpm --filter web cf:typegen

Troubleshooting common Cloudflare deployment issues

Build fails because an environment variable is missing

TurboStarter validates environment variables during build. Make sure build-time values exist locally before cf:build, and runtime values exist in Cloudflare before the deployed Worker starts.

For NEXT_PUBLIC_* values, remember that Next.js inlines them during the build.

Hyperdrive binding is missing

Check that apps/web/wrangler.jsonc includes a hyperdrive entry with binding: "HYPERDRIVE" and a real id. Then rerun:

pnpm --filter web cf:typegen
pnpm --filter web cf:preview

Upload fails because the Worker is too large

Wrangler prints the compressed Worker size during upload. If it exceeds your Cloudflare plan limit, remove unused server dependencies, avoid importing large packages into server routes, or upgrade the Workers plan. See Worker limits.

A package expects unsupported Node.js behavior

The generated wrangler.jsonc enables nodejs_compat, which is required for many Next.js and database use cases on Workers. Some Node.js APIs are still only partially supported, so replace Node-only libraries with HTTP/fetch-based providers when needed.

A route uses the Edge runtime

OpenNext for Cloudflare is designed around the Next.js Node.js runtime on Workers. If you added export const runtime = "edge" to a route, remove it and preview again.

Auth or billing works locally but fails in production

This is usually a URL mismatch. Confirm URL, NEXT_PUBLIC_URL, and BETTER_AUTH_URL all point to your live domain, then update OAuth redirect URLs and billing webhook endpoints before retesting.

For more deployment debugging, see deployment troubleshooting.

Cloudflare vs Vercel for TurboStarter

Both are valid production hosts. The right choice depends on what you optimize for.

FactorCloudflare WorkersVercel
Setup speedModerate โ€” generator + Hyperdrive + R2Fastest โ€” strong Next.js defaults
Edge modelWorker-based global executionManaged Next.js platform
Database strategyHyperdrive to external PostgresExternal Postgres or managed integrations
Object storageNative R2 integrationBring your own S3/R2 provider
Best whenYou want edge + R2 + Hyperdrive in one platformYou want the simplest Next.js deploy path

If you are unsure, start with Vercel for speed. Move to Cloudflare when edge execution, R2, or Cloudflare's broader platform become strategic for your SaaS.

Next steps after deployment

Before sending production traffic to your Cloudflare deployment:

  1. Run through the production deployment checklist
  2. Verify environment variables
  3. Test auth, billing, emails, and storage on the live domain
  4. Set up monitoring and error tracking
  5. Review multiple environments if you maintain staging and production

Official references:

FAQ

Can you deploy TurboStarter to Cloudflare Workers?

Yes. TurboStarter includes a Cloudflare generator that sets up OpenNext, Wrangler, Hyperdrive, and R2 bindings so you can build, preview, and deploy the web app to Cloudflare Workers.

What is the easiest way to deploy TurboStarter to Cloudflare?

Run pnpm turbo gen cloudflare, create Hyperdrive and an R2 cache bucket, configure environment variables, preview with pnpm --filter web cf:preview, then deploy with pnpm --filter web cf:deploy.

Does TurboStarter use Cloudflare Pages or Workers?

TurboStarter uses OpenNext for Cloudflare to deploy the Next.js app as a Cloudflare Worker, not as a Pages-only static deployment. That supports SSR, auth, billing, and other server features your SaaS needs.

Why do I need Hyperdrive for a TurboStarter Cloudflare deployment?

Hyperdrive pools Postgres connections inside Cloudflare's network. That helps serverless-style Worker deployments talk reliably to a regional Postgres database without exhausting connection limits.

What is the R2 bucket used for in TurboStarter on Cloudflare?

The generated setup uses R2 for OpenNext incremental cache through the NEXT_INC_CACHE_R2_BUCKET binding. User uploads should use a separate R2 bucket configured through TurboStarter's storage layer.

Is Cloudflare better than Vercel for TurboStarter?

It depends on your goals. Vercel is usually the fastest default for Next.js. Cloudflare is stronger when you want edge execution, R2, Hyperdrive, and more of your production stack on Cloudflare's platform.

Can I self-host TurboStarter instead of using Cloudflare?

Yes. TurboStarter also supports Docker, Railway, Render, and other deployment targets if you want more infrastructure control.

What should I test before going live on Cloudflare?

Test sign-in, sign-out, organization flows, database reads and writes, billing checkout, webhooks, emails, and file uploads on the preview or staging domain before routing production traffic.

world map
Community

Connect with like-minded people

Join our community to get feedback, support, and grow together with 600+ builders on board, let's ship it!

Join us

Ship your startup everywhere. In minutes.

Skip the complex setups and start building features on day one.

Get TurboStarter