Step 5: Deploy to Cloudflare
import { Steps, Aside } from ‘@astrojs/starlight/components’;
You have a working to-do app locally. Now deploy it to Cloudflare Pages with a real D1 database in a few commands.
Prerequisites
Section titled “Prerequisites”You need:
- A Cloudflare account (free at cloudflare.com)
- The Wrangler CLI authenticated:
npx wrangler login
If you haven’t logged in before:
npx wrangler login# Opens a browser — authorize the CLI to your Cloudflare accountStep 1: Initialize the Production Environment
Section titled “Step 1: Initialize the Production Environment”cruz init reads your cruz.config.ts, creates all the Cloudflare resources your app needs, and generates the wrangler.toml:
cruz init productionThis single command:
- Creates a Cloudflare Pages project named
my-todos - Provisions a D1 database named
my-todos-production-db - Creates a KV namespace for session storage
- Generates
wrangler.tomlwith all binding IDs wired up
You will see output like:
✓ Cloudflare Pages project created: my-todos✓ D1 database created: my-todos-production-db (id: abc123...)✓ KV namespace created: my-todos-production-kv (id: def456...)✓ wrangler.toml updatedStep 2: Set Required Secrets
Section titled “Step 2: Set Required Secrets”Secrets are sensitive values (auth keys, API tokens) stored in Cloudflare’s secret store — not in your repository or config files.
# Generate a strong session secretopenssl rand -base64 32
# Set it in Cloudflare (paste the value when prompted)cruz secrets set AUTH_SECRET --env productionIf you’re using email (Resend, Postmark, etc.):
cruz secrets set RESEND_API_KEY --env productionAll Required Secrets
Section titled “All Required Secrets”| Secret | Required | Description |
|---|---|---|
AUTH_SECRET | Yes | Session signing key — openssl rand -base64 32 |
RESEND_API_KEY | If sending email | Email delivery API key |
GOOGLE_CLIENT_ID | If Google login | OAuth client ID |
GOOGLE_CLIENT_SECRET | If Google login | OAuth client secret |
Step 3: Deploy
Section titled “Step 3: Deploy”cruz deploy productionThis runs three steps in order:
cruz deploy production│├── 1. Build│ └── Vite production build (Cloudflare Pages output)│├── 2. Migrate│ └── Applies pending migrations to your remote D1 database│ (the Todo table migration from Step 2 runs here)│└── 3. Ship └── Deploys to Cloudflare Pages via WranglerThe first deploy takes about 60–90 seconds. Subsequent deploys are faster because Wrangler only uploads changed files.
When it finishes, you will see:
✓ Build complete✓ Migrations applied (1 new migration)✓ Deployed to: https://my-todos.pages.devOpen your app at https://my-todos.pages.dev.
Step 4: Verify
Section titled “Step 4: Verify”Visit your production URL and:
- Sign up for a new account
- Create a todo
- Mark it complete
- Delete it
Everything should work identically to your local dev environment.
Add a Custom Domain
Section titled “Add a Custom Domain”To use your own domain instead of my-todos.pages.dev:
- Update
cruz.config.ts:
environments: { production: { domain: 'todos.mysite.com', vars: { APP_URL: 'https://todos.mysite.com', }, },},- Re-deploy:
cruz deploy production- In your DNS provider, add a
CNAMErecord pointingtodos.mysite.comtomy-todos.pages.dev.
Cloudflare handles SSL certificates automatically. Your app will be available on your custom domain within minutes of DNS propagation.
Deploying Updates
Section titled “Deploying Updates”After making changes to your app, deploy again:
# Make changes, then:cruz deploy productionIf you added a new table or column, cruz db generate (run locally) creates the migration file, and cruz deploy applies it to production automatically.
Staging Environment
Section titled “Staging Environment”It is good practice to have a staging environment to test changes before production:
# One-time setupcruz init staging
# Deploy to staging for testingcruz deploy staging
# After verifying on staging, deploy to productioncruz deploy productionStaging and production each have their own isolated D1 database, so staging data never touches production.
CI/CD with GitHub Actions
Section titled “CI/CD with GitHub Actions”To deploy automatically on every push to main, add a GitHub Actions workflow:
name: Deploy to Production
on: push: branches: [main]
jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' - run: npm ci - run: npm run build - run: npx wrangler pages deploy ./build/client --project-name my-todos env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - run: npx wrangler d1 migrations apply my-todos-production-db --remote env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}Add CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID as repository secrets in GitHub Settings → Secrets and Variables → Actions.
Get your API token from Cloudflare Dashboard → My Profile → API Tokens — create one with Cloudflare Pages: Edit and D1: Edit permissions.
What Runs Where
Section titled “What Runs Where”| Where | What |
|---|---|
| Cloudflare Pages | React Router SSR + tRPC handler (runs at the edge, globally) |
| D1 database | Your SQLite data (replicated globally by Cloudflare) |
| KV namespace | Session storage (globally distributed) |
| Your browser | React client-side hydration + tRPC hooks |
Every request is handled by a Cloudflare Worker in a data center near the user — typically under 50ms cold start globally.
What’s Next
Section titled “What’s Next”Congratulations — you’ve built and deployed a full-stack app with CruzJS.
Continue the tutorial to add more features:
- Organizations (Team Todos) — share todos across a team with org-scoped data
- Background Jobs — send a welcome email when a user signs up
- Testing — write unit and E2E tests for your todos feature
Or explore the rest of the framework:
- CRUD Factory — replace the manual service + router with
createCrud()for standard features - CLI Scaffolding — generate new features with
cruz new feature