Deployment
Hosting is $0: Cloudflare Pages (free, commercial use allowed) + Turso free tier + Resend free tier.
1. Build
Section titled “1. Build”npm run buildOutput lands in dist/. A green build is your deploy gate — never deploy red.
2. Push to Cloudflare Pages
Section titled “2. Push to Cloudflare Pages”npx wrangler pages deploy dist --project-name=your-project3. Set production secrets
Section titled “3. Set production secrets”In the Cloudflare dashboard (or .dev.vars locally — never commit secrets):
TURSO_DATABASE_URL— your hostedlibsql://URLTURSO_AUTH_TOKENDODO_PAYMENTS_API_KEY— live keyDODO_PAYMENTS_WEBHOOK_SECRETDODO_PRODUCTS_JSON— your tier catalog (see Payments)RESEND_API_KEY,EMAIL_FROMBETTER_AUTH_SECRET,BETTER_AUTH_URL(your live URL)SITE_URL(your live URL)
4. Point the webhook
Section titled “4. Point the webhook”In Dodo/Stripe/Lemon Squeezy, set the webhook URL to https://yourdomain.com/api/webhook. Test with a real $1 checkout, then delete the test row.
On Cloudflare Pages, functions/api/webhook.ts is the receiver. A Pages deploy serves dist/client as static assets; it never runs the Astro server route at src/pages/api/webhook.ts. Without the Pages Function the webhook has nowhere to land, so no orders or licenses row is written even though checkout keeps working. The same applies to license verification at functions/api/license/verify.ts.
The Pages Functions read their configuration from Pages secrets and vars, not .env: set DODO_PAYMENTS_WEBHOOK_SECRET, DODO_PRODUCTS_JSON, TURSO_DATABASE_URL, TURSO_AUTH_TOKEN, RESEND_API_KEY, and EMAIL_FROM with wrangler pages secret put <NAME> --project-name <project>.
Checklist
Section titled “Checklist”-
npm run buildgreen - Secrets set in Cloudflare, none committed
- Webhook endpoint registered with your provider
- One live end-to-end purchase verified