Deployment Environments
Deployment Environments
The Bloqr Compiler uses a three-environment model: local, dev, and production. Both the
API Worker (wrangler.toml) and the Angular frontend Worker (frontend/wrangler.toml) follow
the same model.
Overview
flowchart LR
LOCAL["Local\n─────────────────\nwrangler dev :8787\nng serve :4200\nSecrets from .dev.vars\nNo Cloudflare deployment"]
DEV["Dev\n─────────────────\nwrangler deploy --env dev\n*.workers.dev subdomain\nENVIRONMENT=development\nAngular DevTools enabled"]
PROD["Production\n─────────────────\nwrangler deploy\nCustom domain via [[routes]]\nENVIRONMENT=production\nlogpush=true"]
LOCAL -->|"ready to preview on workers.dev"| DEV
DEV -->|"validated, ready to ship"| PROD
| Local | Dev | Production | |
|---|---|---|---|
| Deploy command (API) | deno task wrangler:dev | deno task wrangler:deploy:dev | deno task wrangler:deploy |
| Deploy command (frontend) | pnpm --filter bloqr-frontend run start | deno task ui:deploy:ng:dev | sh scripts/deploy-frontend.sh |
| URL | http://localhost:8787 | https://<worker-name>.workers.dev | custom domain via [[routes]] |
| Angular build | dev server (HMR) | ng build --configuration development | ng build (production config) |
| Angular DevTools | ✅ (dev server always unoptimised) | ✅ (sourceMap, no optimisation) | ❌ (minified, tree-shaken) |
| logpush | N/A (not deployed) | false | true |
| ENVIRONMENT var | set in .dev.vars | "development" | "production" |
Local Development
No Cloudflare deployment is involved. Both workers run locally.
# API Worker → http://localhost:8787deno task wrangler:dev
# Angular dev server → http://localhost:4200 (hot module reload)pnpm --filter bloqr-frontend run start
# Angular Workers preview → http://localhost:4200 (mirrors production SSR)pnpm --filter bloqr-frontend run previewSecrets and local URL overrides live in .dev.vars (gitignored — copy from .dev.vars.example).
Dev Environment
Deploys to the auto-generated *.workers.dev subdomain without touching the production
custom domain. Use this environment to:
- Preview changes live on Cloudflare infrastructure
- Debug with Angular DevTools (source maps enabled, no minification)
- Test before merging to production
Deploy the API Worker to dev
deno task wrangler:deploy:dev# equivalent: deno run -A npm:wrangler deploy --env devDeploy the Angular frontend to dev
deno task ui:deploy:ng:dev# equivalent: pnpm --filter bloqr-frontend run build:dev# + pnpm --filter bloqr-frontend run deploy:devOr step by step:
# Build with development configuration (sourceMap=true, optimization=false)pnpm --filter bloqr-frontend run build:dev # ng build --configuration development
# Deploy to *.workers.dev (--env dev skips the [[routes]] custom domain block)pnpm --filter bloqr-frontend run deploy:dev # wrangler deploy --env devWhat [env.dev] does
# wrangler.toml (API Worker) — and frontend/wrangler.toml mirrors this[env.dev]logpush = false # disable Logpush for the dev subdomain
[env.dev.vars]ENVIRONMENT = "development"- Routes are NOT inherited — the dev environment is accessible only via the
*.workers.devsubdomain. The[[routes]]custom domain block in the top-level config is not applied. - Bindings ARE inherited — KV namespaces, D1, R2, Queues, and service bindings
are all shared with production by default. Create separate dev resources and
override them inside
[env.dev]when isolation is needed. - Secrets are shared —
wrangler secret putapplies to all environments. To set an env-specific secret, usewrangler secret put <KEY> --env dev.
Production Environment
The top-level wrangler.toml / frontend/wrangler.toml IS the production configuration.
No [env.production] block is needed.
# API Workerdeno task wrangler:deploy
# Angular frontend (preferred — builds, injects CF Analytics token, deploys)sh scripts/deploy-frontend.shProduction deploys to the custom domains declared in the [[routes]] block of each
wrangler.toml. See URL Management for how to change
these URLs.
URL Configuration
All public-facing URLs are managed as [vars] entries and kept in sync across both
wrangler.toml files. Replace the placeholder values below with your real domains:
| Variable | Purpose | Example value |
|---|---|---|
URL_FRONTEND | Angular frontend worker | https://app.<your-domain> |
URL_API | API / backend worker | https://api.<your-domain> |
URL_DOCS | Documentation site | https://docs.<your-domain> |
URL_LANDING | Marketing landing page | https://<your-domain> |
CANONICAL_DOMAIN | Domain used for crawl-protection noindex logic | <your-domain> |
CANONICAL_DOMAIN controls the X-Robots-Tag: noindex, nofollow header. Any request
arriving at a hostname that is neither <CANONICAL_DOMAIN> nor a subdomain of it will
receive the noindex header. This prevents *.workers.dev and other temporary hostnames
from being indexed by search engines while a custom domain is active.
Important:
CANONICAL_DOMAINmust be the full domain you use — not just the registrable root. Forapi.bloqr.dev, setCANONICAL_DOMAIN = "bloqr.dev"so that all*.bloqr.devsubdomains are treated as canonical. Setting it todevwould match every.devTLD hostname, which is wrong.
To swap all URLs at once, run:
# Interactive — prompts for root domain and (optionally) canonical domaindeno task domain:swap
# Non-interactive examplesdeno task domain:swap -- --domain bloqr.devdeno task domain:swap -- --domain bloqr.dev --canonical bloqr.dev
# Preview changes without writing any filesdeno task domain:swap -- --domain bloqr.dev --dry-runThe script updates both [vars] and [env.dev.vars] in each wrangler.toml, so
both environments stay in sync after a single run.
For detailed URL change steps, see URL Management.
The [env.dev.vars] block intentionally repeats these values. The dev environment
runs on *.workers.dev, but the app still references canonical production URLs (e.g.
for OpenGraph meta tags and API base URLs). Override them if you also have a dev
custom domain.
TOML Scoping Rules
Two Wrangler-specific TOML gotchas apply to both wrangler.toml files:
1. Array-of-tables must come AFTER all bare top-level keys
In TOML, once you open an array-of-tables header ([[routes]], [[rules]], etc.), every
bare key = value that follows — until the next [table] or [[table]] header — is parsed
as a field of that array entry, not as a top-level key. This means if main, no_bundle,
compatibility_date, etc. appear after [[routes]], they become route fields and Wrangler
rejects the config.
Correct structure:
name = "my-worker"main = "worker.ts"workers_dev = truelogpush = true# ... all other bare top-level keys ...
[[routes]] # array-of-tables only AFTER all bare keyspattern = "api.<your-domain>"custom_domain = true
[[rules]] # same rule applies to [[rules]]type = "ESModule"globs = ["**/*.mjs"]
[vars] # standard tables can followENVIRONMENT = "production"2. Custom domain routes must be bare hostnames
For custom_domain = true route entries, Cloudflare only accepts a bare hostname — no
paths or wildcards:
# ✅ Correct[[routes]]pattern = "api.<your-domain>"custom_domain = true
# ❌ Wrong — path/wildcard not allowed with custom_domain = true[[routes]]pattern = "api.<your-domain>/*"custom_domain = trueFurther Reading
- Cloudflare Workers Architecture — two-worker split, bundled vs independent SSR modes, CI/CD deploy flow
- URL Management — single source of truth for service URLs
- Environment Configuration — two-track env system
(shell vs Wrangler),
.dev.vars, secrets management - Wrangler Environments docs