Deploy mkfast-template(TanStarter-family) projects to Cloudflare Workers.Core value: dynamically trim deployment steps based on the components actually enabled in the project, avoiding copying the full-version docs wholesale, which would cause a trimmed project to run empty commands or even error out.
Scope
Is: projects derived from the mkfast-template template, including the full SaaS form, trimmed blog versions, or any intermediate form.
Is not: any Cloudflare Workers project (in that case the wrangler skill is enough).
Identification markers (any one is sufficient):
- The root directory has
wrangler.jsonc+package.json package.jsoncontainingwrangler+@cloudflare/vite-plugin+@tanstack/react-startany one of- The directory contains mkfast characteristic files:
drizzle.config.ts/src/server.ts/src/db/auth.schema.ts
Important principles
- Assess before executing — Phase 1 must be completed before any deploy command, generating a trimmed step checklist. Directly
pnpm run deploydoes not count as work completed - Do not copy the official docs wholesale — the full mkfast docs assume D1 + R2 + secret bulk,and a trimmed project running these steps will error or waste time(for example, running
db:migrate:remotewhen there is no drizzle config will fail to find the migrations directory) - secret bulk is a gating step — the project has server-side secrets (
.env.productioncontaining non-VITE_*variables) that must be pushed to Cloudflare before deploy, otherwise it will crash at runtime - Production deployment requires explicit confirmation — before executing
pnpm run deployin Phase 3, you must use AskUserQuestion to get the user's final confirmation (it affects the production domain) - Domain status determines the routes strategy — when the domain is not hosted on Cloudflare, you cannot directly deploy a config containing
routes; you must first remove routes, deploy to get the default domain, then bind it in the Dashboard (seereferences/tailoring.mdRoutes configuration strategy table) - Security first —
.env*must go into.gitignore;any secret that has appeared in conversations / screenshots / Slack / commit messages is considered leaked, and must be rotated immediately (see Phase 1.5 security baseline + Phase 4 Secret rotation reminder) - Template placeholders must be checked — mkfast-template-derived projects'
wrangler.jsonchas 4 template default values that must be changed(name/routes.pattern/database_id/bucket_name) + 1 that must be disabled (logpush: truewill always report code 10023 on the Free/Pro plan). Seereferences/components.md§0 placeholder identification table;deploying without checking will write to the template author's D1 or the deploy will fail
Workflow (4 phases)
Phase 1 — Automatic assessment (read-only)
Step 1.0 (mandatory, blocks subsequent progress): check against references/components.md §0 template default placeholder identification table for wrangler.jsonc + package.json diff。any unreplaced template default values——name="mkfast-template" / routes[0].pattern="demo.tanstarter.dev" / database_id="dc34f04a-3445-4b5c-bf61-c4ec2328e239" / r2.bucket_name="mkfast-template" / logpush: true— all of them directly block entry into Phase 2。
This isthe most important prerequisite gate of this skill: Deploying without checking will (a) write to the template author's D1 database; (b) fail the deploy with code 10023 (a Logpush enterprise feature); (c) cause route conflicts. All of these are pitfalls that first-time deployments of projects derived from mkfast-templateare almost certain to hit.
Read the project state and identify the "project profile".At minimum, read these 5 files:
| What to read | What to extract |
|---|---|
wrangler.jsonc |
name / routes / d1_databases / r2_buckets / send_email / other bindings |
package.json |
name + whether the dependencies include drizzle-orm / better-auth / stripe / creem / @beehiiv/sdk |
.env.example |
All supported variable categories (refer to the references/components.md detection signal table) |
.env.production(if present) |
Configured variables vs. unfilled (empty string) variables |
.env.local(if present) |
CLOUDFLARE_ACCOUNT_ID + CLOUDFLARE_API_TOKEN whether it is ready |
According to references/components.md , determine one by one whether each of the 12 component categories is enabled, and generate the "project profile" output (for the format, refer to templates/deploy-checklist.md):
- Project form: full SaaS / trimmed-down blog / intermediate form
- Enabled components checklist
- Environment variable gap table
- Trimmed-down checklist of steps to execute (number of steps)
Phase 1.5 — Security baseline (read-only + create .gitignore if necessary)
4 items that must be checked before deployment; fill in whichever is missing.Not letting secrets leak out of the repository is the bottom line:
| Item | Check command | Fix when it fails |
|---|---|---|
.gitignore exists and contains .env* |
grep -E "\.env" .gitignore |
If it does not exist,create it immediatelycontaining .env、.env.local、.env.*.local、.env.production、.dev.vars |
| .env.local is not in git history | git log --all --full-history -- .env.local output is empty |
has already been committed → use git filter-repo to clear it + immediately rotate all secrets involved in |
wrangler.jsonc has no hard-coded secrets |
grep should not show sk_live_ / re_live_ / whsec_ , etc. |
Delete hard-coded ones; route everything through wrangler secret put |
.env.production no plaintext production secrets are about to be committed |
`cat .gitignore | grep .env.production` |
When a trimmed-down project has no .gitignore(I have seen projects that have not yet run git init but have already written .env.local ), first create .gitignore before continuing to Phase 2.
Phase 2 — Decision points (AskUserQuestion)
Ask the user based on the Phase 1 / 1.5 profile, choosing 1-5 questions depending on the situation:
- Target user region(strongly recommended to ask first): Global / Overseas only / Mainly mainland China / Both domestic and overseas
- If mainland China is selected → ⚠️ Warning: Cloudflare has no PoP nodes in mainland Chinano PoP nodes(unless Enterprise Edition + JD Cloud partnership); access experience will be intermittent;recommend referring to
references/cn-access.mdswitchover plan(EdgeOne / Vercel + domestic CDN / dual deployment)
- If mainland China is selected → ⚠️ Warning: Cloudflare has no PoP nodes in mainland Chinano PoP nodes(unless Enterprise Edition + JD Cloud partnership); access experience will be intermittent;recommend referring to
- Domain status(must ask):
<domain>Is it hosted on Cloudflare? Three options: already hosted / not hosted (use the default *.workers.dev for now) / not purchased yet - D1 / R2 resources(full version only): Have they already been created in Cloudflare? If not, do you agree to now
wrangler d1 create/wrangler r2 bucket create? - Optional features: Should Giscus / analytics / Webhook / Crisp be enabled this time?
- CI automation: Whether to sync-configure GitHub Actions auto-deploy?
For the pruning decision tree, see references/tailoring.md。
Phase 3 — Execution (according to the pruned checklist)
Step 0 — Sanity (strongly recommended):
# 清掉过往 dev 留下的 dist + .vite 缓存
# 避免 chokidar 在 dev 中 EINTR 报错(曾导致 dev server 起不来)
rm -rf dist .vite node_modules/.vite
# 验证 wrangler 凭证已加载(推荐用 .env.local + 一次 source 而非 wrangler login)
# wrangler 不会自动读 vite 系的 .env.local,必须 export 到当前 shell:
set -a && . ./.env.local && set +a && pnpm wrangler whoami
Recommended to add an alias once in
package.jsonto solve it:"deploy:cf": "set -a && . ./.env.local && set +a && pnpm run build && wrangler deploy"Afterwards, just use
pnpm deploy:cf, avoiding the need to export before every wrangler command.
Required for all projects:
pnpm wrangler whoamiVerify login (when not logged in, check.env.localcredentials or promptwrangler login)- Check token permissions: according to the enabled components, check
references/components.md's §API Token permission complete matrix (Workers Scripts / D1 / R2 / Workers Routes / Email / KV, etc.); if permissions are missing, go directly to Dashboard → My Profile → API Tokens and edit the existing token to add permissions (no need to recreate) - AskUserQuestion final confirmation →
pnpm run deploy
Only do this when the corresponding component is detected(according to references/components.md detailed steps):
- (has D1)
pnpm wrangler d1 create <name>→ write back towrangler.jsonc'sdatabase_id→pnpm db:migrate:remote - (has R2)
pnpm wrangler r2 bucket create <name>→ write back tobucket_name - (has send_email) Cloudflare Dashboard enable Email Routing + verify from email
- (has Better Auth)
pnpm dlx @better-auth/cli@latest secretgenerate →wrangler secret put BETTER_AUTH_SECRET - (has server-side secret)
pnpm wrangler secret bulk .env.production - (has payment) Stripe/Creem live key + price ID + webhook endpoint configuration
Only when the user selected CI in Phase 2:
- Create
.github/workflows/deploy.yml+ prompt to go to GitHub Settings → Secrets and addCLOUDFLARE_ACCOUNT_ID/CLOUDFLARE_API_TOKEN/ allVITE_*
Phase 4 — Verification
# 在线验证(首次)
curl -s -o /dev/null -w "HTTP %{http_code} | size %{size_download}B | time %{time_total}s\n" https://<domain>
# 5-10 秒后再跑一次(验证缓存 + 二次访问性能)
curl -s -o /dev/null -w "HTTP %{http_code} | size %{size_download}B | time %{time_total}s\n" https://<domain>
Expected:
| Metric | First time | Second time (warm) |
|---|---|---|
| HTTP code | 200 | 200 |
| size | > a few KB (SSR rendered successfully) | Same as first time or smaller (cache hit) |
| time | < 3s (including cold start + CDN propagation) | < 500ms (PoP hit) |
Additional checks:
- In the deploy output,
Total Upload: X KiB / gzip: Y KiB→Y < 3072(3 MB free plan limit) - Cloudflare Dashboard → Workers & Pages →
<worker-name>→ Logs → no runtime error - The first visit to a custom domain may have a 30s CDN propagation delay
30-minute post-launch observation checklist:
| Check item | Command / Location | Expected |
|---|---|---|
| 5xx response rate | Dashboard → Workers & Pages → <worker> → Logs → filter status >= 500 |
0 entries |
| Version number matches | Dashboard → <worker> → Deployments |
Latest Version ID matches the deploy output |
| Analytics traffic | Dashboard → <worker> → Analytics (or third-party RUM) |
Normal requests are coming in |
| D1 table access (if enabled) | wrangler d1 execute <name> --remote --command "SELECT COUNT(*) FROM user" |
Numbers are returned without errors |
| R2 bucket access (if enabled) | wrangler r2 object list <bucket> |
List returns without errors |
Rollback plan:
# 列出历史版本
pnpm wrangler deployments list
# 回滚到上一版(不传 id)
pnpm wrangler rollback
# 回滚到指定版本
pnpm wrangler rollback <version-id>
Note:
- D1 migrations are forward-only; schema rollback requires manually writing a down migration and
wrangler d1 executeapplying it - R2 has no versioning concept; restore from backup as needed (only if
wrangler r2 object getbackup was done before deployment) - Secret rollback:
wrangler secret put <KEY>overwrite with the old value, orwrangler secret delete <KEY>clear
Secret rotation reminder (important): If, during this deployment, a token / API key / database secret has been in any of the following channelsappeared:
- Claude / ChatGPT and other chat histories
- Slack / Lark / WeChat messages
- Screenshots / screen recordings
- Code comments / commit messages
→ immediately go to the Cloudflare Dashboard / third-party service backend and roll it(generate a new value to replace the old value). If the token string has changed, remember to update .env.local and GitHub Secrets.
For troubleshooting errors, see references/troubleshoot.md。
Output format
After each phase ends, give the user a concise report, e.g.:
Phase 1 project profile output (example)
## Phase 1 — 项目画像
**形态**:裁剪版博客
**启用组件**:无(纯 SSR + 博客)
**待执行步骤**:2 步(whoami + deploy)
**跳过**:D1 / R2 / Email / Auth / Pay / Secret bulk / Migrate
Final checklist (example)
✅ wrangler 登录(账户 zhihui)
✅ pnpm run deploy(gzip 471 KB / 17ms 启动)
✅ HTTP 200 验证通过
⚠️ 可选未启用:Giscus / Plausible
Tool files
-
references/components.md— mkfast-template 12 categories of component detection signals → deployment step mapping table- §0 Template default placeholder identification table (required check in Phase 1 Step 1.0)
- §API Token permission complete matrix (quick reference table for checking permissions by enabled component)
- §How to infer the secret list when there is no .env.example (infer by reading src/env/*.ts)
-
references/tailoring.md— Full version vs trimmed version vs intermediate-form trimming decision tree + decision matrix quick reference table- §Pitfall:
enable=falseThe D1 binding still needs to be retained (module-load-time callgetDb()) - §Clarification:
enable=false≠ tree-shake (enable only controls UX; the bundle still contains the SDK)
- §Pitfall:
-
references/troubleshoot.md— 11 common errors + troubleshooting commands + fixes (including #10 Logpush / #11 already exists) -
references/cn-access.md— 4 tiers of solutions for optimizing access from mainland China (Cloudflare settings / image hosting / EdgeOne reverse proxy / dual deployment) -
templates/deploy-checklist.md— Phase 1 project profile output template + final report template + AskUserQuestion template -
templates/stub-no-d1-r2.md— Code stub template to completely disable D1/R2 (pasteable version with changes to 4 files)