Deploying NVX Stack Builder to Cloudflare Workers

View raw
On this page

Production (2026-10-11, v0.6.0): live at https://devstack.bid (apex). It is a Workers Custom Domain of Worker nvx-stack-builder in account 2d92bd5b25768fa9093d6adc0a8887fc; www.devstack.bid is also attached and returns 308 to the apex.

  • v0.6.0 deployed 2026-10-11 11:18 ICT (in-app docs at /docs, /llms.txt, DevStack signature). Version 7ddd9404-a31f-4e31-8460-82b3b90c7d0e. Then the D1 binding DB (database nvx-db) was added the same day; that build is now current: 450890b7-5432-4795-afbc-1c7ebd241bff. Rollback targets: 7ddd9404-a31f-4e31-8460-82b3b90c7d0e (v0.6.0), then 671b6b3b-1074-40cb-84b0-0df2681b69ce (v0.5.1), then d369f2b6-863a-4e44-8f9e-b5551e18361a (v0.5.0) and 107ab78e-2cdd-4159-95e7-e8fcb89130aa.
  • workers.dev fallback: https://nvx-stack-builder.examplessdk.workers.dev. The account's subdomain was renamed from space6 to examplessdk on 2026-10-11, so the old space6 URL no longer resolves.
  • HSTS is now max-age=31536000, without includeSubDomains/preload.
  • Details, created records and token permissions: operations/custom-domain.md.

v0.5.0 (2026-10-11), history: first deployed at https://nvx-stack-builder.space6.workers.dev; that URL was retired by the subdomain rename (Worker nvx-stack-builder, account 2d92bd5b25768fa9093d6adc0a8887fc "Examplessdk@gmail.com's Account", workers.dev subdomain space6, no routes or custom domains). Deployed 2026-10-11 08:47 ICT from commit f995d24 (released as tag v0.5.0). Verified live:

  • all pages 200 (including the 10 template pages); /templates/unknown and /definitely-missing return a real 404
  • security headers on pages, and Cache-Control on /brand/*
  • manifest, favicon, icons and the OG image are served
  • no horizontal scroll at 1440px or 390px, in Thai or English; no console errors
  • the mascot animates and goes static under reduced motion
  • /api/plan: 415/413/400; a Thai prompt returns source: "openai" (gpt-6.1-sol); after 20–21 POSTs/min it returns 429 with Retry-After: 60

v0.5.0 versions: d369f2b6-863a-4e44-8f9e-b5551e18361a (code + OPENAI_API_KEY secret, the rollback target before v0.5.1); 107ab78e-2cdd-4159-95e7-e8fcb89130aa (same code before the secret was set, so the AI agent falls back to the offline planner). That account has no earlier versions. The only other Worker in the account, odd-violet-5e66, was not touched.

Previous test deployment (unchanged, still live): https://nvx-stack-builder.agen-sdk-work.workers.dev (account f70d35188a3c56b9781538c73a86e04e, v0.3.0, version 0fb9ecd4-d788-468c-8843-559545c357e3, earlier 9b9b678a-f0b4-4727-96d7-e5f81fc00391). It is left untouched on purpose and still serves the v0.3.0 design.


English#

1. Requirements#

  • Node.js 22+ for Wrangler 4 (wrangler refuses Node 20). Next.js itself runs on Node 20.9+.
  • A Cloudflare account and an API token (see Token permissions).
  • npm install. It runs postinstall → scripts/patch-opennext.mjs, which patches @opennextjs/cloudflare so it also loads Next 16.4's preview-props.json. The patch is idempotent and safe to re-run.

2. Local development#

bash
npm install
cp .dev.vars.example .dev.vars    # put OPENAI_API_KEY=... (never commit; gitignored)
npm run dev                       # Next.js dev server, http://localhost:3000
npm run preview                   # build for Workers + run in workerd, http://localhost:8787

npm run dev also uses .dev.vars and the Cloudflare bindings, through initOpenNextCloudflareForDev().

Checks: npm run lint && npm run typecheck && npm test && npm run cf:build.

3. Configuration#

Name Kind Where Default
OPENAI_API_KEY secret wrangler secret put / .dev.vars none → offline planner
OPENAI_MODEL var wrangler.jsonc vars gpt-6.1-sol
OPENAI_MAX_OUTPUT_TOKENS var wrangler.jsonc 6000 (hard cap 16000)
OPENAI_TIMEOUT_MS var wrangler.jsonc 75000 in wrangler, 60000 in code
NVX_PLAN_LIMITER rate-limit binding wrangler.jsonc ratelimits (namespace 4712) 20 req / 60 s per IP, all POSTs
NVX_PLAN_AI_LIMITER rate-limit binding wrangler.jsonc ratelimits (namespace 4711) 5 req / 60 s per IP, shared server key only
PLAN_LIMIT_PER_MIN, PLAN_AI_LIMIT_PER_MIN var only used by the in-memory fallback (non-Workers hosts) 20 / 5

Rate-limit bindings are declared in config only. They are not a separate dashboard resource, and they are created when the Worker is deployed. Counters are per Cloudflare location and eventually consistent, so very fast bursts may let a few extra requests through, which is fine for abuse control. Clients are keyed by IP, and IPv6 clients by their /64 prefix.

Set the secret via stdin so it never appears in shell history or logs:

bash
printenv OPENAI_API_KEY | npx wrangler secret put OPENAI_API_KEY

Users can still paste their own OpenAI key in the UI. It travels only in the x-openai-key header of that single request, isn't rate-limited by the AI limiter, and is never stored or logged.

4. Deploy (workers.dev only)#

bash
export CLOUDFLARE_API_TOKEN=...          # only the token string, nothing else
export CLOUDFLARE_ACCOUNT_ID=2d92bd5b25768fa9093d6adc0a8887fc   # production account (devstack.bid, examplessdk.workers.dev)
# previous test account (v0.3.0, agen-sdk-work.workers.dev): f70d35188a3c56b9781538c73a86e04e

# 1) make sure the worker name is free (should say it does not exist)
npx wrangler deployments list --name nvx-stack-builder
#    If it exists and is not ours: change "name" (and the WORKER_SELF_REFERENCE
#    service) in wrangler.jsonc to "nvx-stack-builder-staging".

# 2) build + deploy (creates the Worker on first deploy)
npm run deploy                            # = opennextjs-cloudflare build && opennextjs-cloudflare deploy

# 3) secret (after the worker exists)
printenv OPENAI_API_KEY | npx wrangler secret put OPENAI_API_KEY

# 4) verify
curl -sI https://nvx-stack-builder.<subdomain>.workers.dev/ | grep -iE 'strict-transport|content-security'
curl -s https://nvx-stack-builder.<subdomain>.workers.dev/api/plan   # {"serverKeyConfigured":true,...}

wrangler.jsonc sets workers_dev: true, preview_urls: false and no routes, so the deploy never touches any zone, DNS or custom domain.

5. Rollback#

bash
npx wrangler deployments list --name nvx-stack-builder
npx wrangler rollback <version-id> --name nvx-stack-builder -m "reason"
# or stop serving the test worker entirely:
npx wrangler delete --name nvx-stack-builder      # only this worker

Secrets and vars belong to versions. A rollback restores the previous version's code and bindings.

6. Later: binding agents-sdk.space (NOT done; plan only)#

Current state: zone agents-sdk.space (dfb531c381f8e6dd75082a6fe964f71b) has a proxied apex AAAA 100:: placeholder record. That pattern is used for Worker routes, and the existing Worker agents-sdk-space most likely serves the apex through a route or custom domain.

Steps, when the owner approves:

  1. Decide: replace agents-sdk-space on the apex, or use a subdomain such as stack.agents-sdk.space (no conflict, recommended for a first rollout).
  2. Token: add Zone › Zone: Read, Zone › Workers Routes: Edit and Zone › DNS: Edit for that zone, and include the account 9d56…34d1.
  3. If you take over the apex, first remove the apex route or custom domain from agents-sdk-space (Workers → agents-sdk-space → Settings → Domains & Routes). Delete the AAAA 100:: record only if no route still needs it. A custom domain creates its own DNS record and conflicts with an existing record for the same name.
  4. Add to wrangler.jsonc:
    jsonc
    "routes": [{ "pattern": "stack.agents-sdk.space", "custom_domain": true }]
    then npm run deploy, or use Dashboard → Worker → Domains & Routes → Add Custom Domain.
  5. HSTS: since v0.5.1 the app sends Strict-Transport-Security: max-age=31536000 (no includeSubDomains), because it now runs on the devstack.bid apex. The older value max-age=31536000; includeSubDomains would have applied as described next. On the apex this applies to all subdomains, so make sure all of them serve HTTPS. Don't add preload until you're sure.
  6. Optionally set workers_dev: false once the domain works.

7. Token permissions#

Each test account uses its own account-scoped token. The v0.3.0 token (restless-heart-ac34) covers only f70d…e04e. The v0.5.0 deploy used a separate token scoped to 2d92…87fc, passed only as CLOUDFLARE_API_TOKEN to wrangler and never printed or stored. Its /user/tokens/verify returns non-JSON, which is normal for account tokens: check them with /accounts/<id>/tokens/verify or wrangler whoami. To deploy to yet another account:

Minimum for the test deploy (account-scoped token, resources must include account 9d56815c97f330febf9d6a0bb7f234d1):

  • Account › Workers Scripts: Edit (upload, secrets, versions, rollback, rate-limit bindings)
  • Account › Account Settings: Read (whoami, workers.dev subdomain)
  • Optional: Account › Workers Observability: Edit, Workers Tail: Read

8. Files added/changed for Cloudflare#

wrangler.jsonc, open-next.config.ts, cloudflare-env.d.ts, .dev.vars.example, public/_headers, scripts/patch-opennext.mjs, next.config.ts (headers, 404 rewrite, dev bindings), src/lib/server/{env,http,rate-limit}.ts, src/lib/security-headers.ts, src/data/template-ids.ts, src/app/api/plan/route.ts, src/lib/ai/openai.ts, tests/production.test.ts, vitest.config.mts, package.json, .gitignore.


ภาษาไทย#

Production (11 ต.ค. 2026, v0.6.0): เสิร์ฟที่ https://devstack.bid (apex) ผ่าน Workers Custom Domain ของ Worker nvx-stack-builder และ www.devstack.bid redirect 308 ไป apex เวอร์ชันปัจจุบัน 7ddd9404-a31f-4e31-8460-82b3b90c7d0e (v0.6.0, deploy 11:18 ICT; ก่อนหน้า 671b6b3b-1074-40cb-84b0-0df2681b69ce v0.5.1 ใช้ rollback ได้ แล้ว d369f2b6-863a-4e44-8f9e-b5551e18361a) workers.dev สำรองที่ https://nvx-stack-builder.examplessdk.workers.dev (บัญชีเปลี่ยน subdomain จาก space6 เป็น examplessdk) HSTS เหลือ max-age=31536000 ไม่มี includeSubDomains รายละเอียดดู operations/custom-domain.md

สถานะ v0.5.0 (11 ต.ค. 2026): deploy ครั้งแรกที่ https://nvx-stack-builder.space6.workers.dev (URL นี้ใช้ไม่ได้แล้ว) (Worker nvx-stack-builder บัญชี 2d92bd5b25768fa9093d6adc0a8887fc "Examplessdk@gmail.com's Account" subdomain space6 ใช้ workers.dev เท่านั้น) deploy เมื่อ 11 ต.ค. 2026 08:47 (ICT) จาก commit f995d24 (tag v0.5.0) ตรวจบนเว็บจริงผ่านทุกข้อ: ทุกหน้าได้ 200, 404 จริง, security headers, manifest/favicon/OG image, ไม่มี horizontal scroll ทั้งไทยและอังกฤษ, prompt ภาษาไทยได้ source: "openai", rate limit ตอบ 429 พร้อม Retry-After: 60 เวอร์ชันสำหรับ rollback: d369f2b6-863a-4e44-8f9e-b5551e18361a (ปัจจุบัน) และ 107ab78e-2cdd-4159-95e7-e8fcb89130aa (โค้ดเดียวกันแต่ยังไม่ตั้ง secret) Worker เดิม odd-violet-5e66 ในบัญชีนั้นไม่ถูกแตะ

deploy ทดสอบเดิม (v0.3.0) ที่ https://nvx-stack-builder.agen-sdk-work.workers.dev บัญชี f70d35188a3c56b9781538c73a86e04e ยังอยู่และไม่ถูกแก้ไข (เวอร์ชัน 0fb9ecd4-d788-468c-8843-559545c357e3)

1. สิ่งที่ต้องมี#

  • Node.js 22 ขึ้นไป สำหรับ Wrangler 4
  • บัญชี Cloudflare และ API token ที่มีสิทธิ์ตามหัวข้อ 7
  • npm install จะรัน scripts/patch-opennext.mjs ให้อัตโนมัติ เพื่อแก้ adapter ให้รองรับ preview-props.json ของ Next 16.4

2. รันบนเครื่อง#

bash
npm install
cp .dev.vars.example .dev.vars   # ใส่ OPENAI_API_KEY (ห้าม commit)
npm run dev        # http://localhost:3000
npm run preview    # รันใน Workers runtime ที่ http://localhost:8787

3. ตัวแปรและ secret#

  • OPENAI_API_KEY เป็น secret ตั้งผ่าน stdin เท่านั้น: printenv OPENAI_API_KEY | npx wrangler secret put OPENAI_API_KEY
  • OPENAI_MODEL (ค่าเริ่มต้น gpt-6.1-sol), OPENAI_MAX_OUTPUT_TOKENS (6000, สูงสุด 16000), OPENAI_TIMEOUT_MS ตั้งใน vars ของ wrangler.jsonc
  • Rate limit: NVX_PLAN_LIMITER 20 ครั้งต่อนาทีต่อ IP สำหรับทุก POST และ NVX_PLAN_AI_LIMITER 5 ครั้งต่อนาทีต่อ IP เฉพาะเมื่อใช้ key ของเซิร์ฟเวอร์ binding ทั้งสองถูกสร้างพร้อมการ deploy
  • key ที่ผู้ใช้ใส่เองถูกใช้แค่ในคำขอนั้น ไม่ถูกเก็บและไม่ถูกบันทึก log

4. Deploy (workers.dev เท่านั้น)#

bash
export CLOUDFLARE_API_TOKEN=...      # ใส่เฉพาะตัว token
export CLOUDFLARE_ACCOUNT_ID=2d92bd5b25768fa9093d6adc0a8887fc   # บัญชี production (devstack.bid) · บัญชีเดิม v0.3.0: f70d35188a3c56b9781538c73a86e04e
npx wrangler deployments list --name nvx-stack-builder   # ตรวจว่าชื่อยังว่าง ถ้าไม่ว่างให้ใช้ nvx-stack-builder-staging
npm run deploy
printenv OPENAI_API_KEY | npx wrangler secret put OPENAI_API_KEY

ไฟล์ config ไม่มี routes จึงไม่แตะ zone, DNS หรือ custom domain ใด ๆ

5. ย้อนเวอร์ชัน (rollback)#

bash
npx wrangler deployments list --name nvx-stack-builder
npx wrangler rollback <version-id> --name nvx-stack-builder

6. ผูกโดเมน agents-sdk.space ภายหลัง (ยังไม่ได้ทำ)#

  1. เลือกว่าจะใช้ apex แทน Worker agents-sdk-space เดิม หรือใช้ subdomain เช่น stack.agents-sdk.space (แนะนำ เพราะไม่ชนกับของเดิม)
  2. เพิ่มสิทธิ์ token: Zone Read, Workers Routes Edit, DNS Edit บน zone dfb531c381f8e6dd75082a6fe964f71b
  3. ถ้าจะใช้ apex ต้องถอด route หรือ custom domain ของ agents-sdk-space ก่อน และพิจารณาลบ record AAAA 100:: (proxied) ที่เป็น placeholder ของ route เดิม
  4. เพิ่ม "routes": [{ "pattern": "stack.agents-sdk.space", "custom_domain": true }] แล้ว npm run deploy
  5. HSTS: ตั้งแต่ v0.5.1 ไม่ส่ง includeSubDomains เพราะแอปอยู่บน apex ถ้าจะเพิ่มกลับ ทุก subdomain ต้องรองรับ HTTPS ก่อน

7. สิทธิ์ของ token ที่ต้องใช้#

token ต้องมีบัญชี 9d56815c97f330febf9d6a0bb7f234d1 อยู่ใน resources และมีสิทธิ์ Account › Workers Scripts: Edit กับ Account › Account Settings: Read (ไม่บังคับ: Workers Observability Edit, Workers Tail Read)

8. ไฟล์ที่เพิ่มหรือแก้#

ดูรายการในหัวข้อ 8 ของภาษาอังกฤษด้านบน

Source: docs/DEPLOY.md · /docs/deploy.md