
Mkfast Deploy
- 126 installs
- 303 repo stars
- Updated April 20, 2026
- iamzhihuix/happy-claude-skills
Ship MkDocs or static sites fast with scripted build, preview, and deploy steps so agents publish docs without hand-rolling CI each time.
About
mkfast-deploy from happy-claude-skills packages a fast MkDocs deployment workflow for Claude agents. It guides scripted build, preview, and publish so documentation sites reach hosting without bespoke CI each project. Best for teams shipping static docs alongside SaaS or API products.
- MkDocs-oriented fast deploy workflow
- Automates build and publish steps
- Reduces manual CI setup for docs sites
- Agent-friendly repeatable release script
- Targets static site hosting pipelines
Mkfast Deploy by the numbers
- 126 all-time installs (skills.sh)
- +2 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #504 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/iamzhihuix/happy-claude-skills --skill mkfast-deployAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 126 |
|---|---|
| repo stars | ★ 303 |
| Last updated | April 20, 2026 |
| Repository | iamzhihuix/happy-claude-skills ↗ |
What it does
Ship MkDocs or static sites fast with scripted build, preview, and deploy steps so agents publish docs without hand-rolling CI each time.
Files
Mkfast Deploy
把 mkfast-template(TanStarter 系)项目部署到 Cloudflare Workers。核心价值:根据项目实际启用的组件动态裁剪部署步骤,避免照搬完整版文档导致裁剪版项目跑空命令甚至报错。
适用范围
是:基于 mkfast-template 模板派生的项目,包括完整 SaaS 形态、裁剪版博客、或任意中间形态。
不是:任意 Cloudflare Workers 项目(这种情况用 wrangler skill 就够)。
判断标志(满足任一即可):
- 根目录有
wrangler.jsonc+package.json package.json含wrangler+@cloudflare/vite-plugin+@tanstack/react-start中任一- 目录里有 mkfast 特征文件:
drizzle.config.ts/src/server.ts/src/db/auth.schema.ts
重要原则
1. 先评估再执行 — 任何 deploy 命令前必须完成 Phase 1,生成裁剪后的步骤清单。直接 pnpm run deploy 不算工作完成 2. 不要照搬官方文档 — mkfast 完整版文档假设了 D1 + R2 + secret bulk,裁剪版项目跑这些步骤会报错或浪费(例如无 drizzle 配置时跑 db:migrate:remote 会找不到 migrations 目录) 3. secret bulk 是 gating step — 项目有服务端 secret(.env.production 含非 VITE_* 变量)必须在 deploy 前推到 Cloudflare,否则运行时崩溃 4. 生产部署需明确确认 — Phase 3 执行 pnpm run deploy 前必须用 AskUserQuestion 让用户最终确认(影响生产域名) 5. 域名状态决定 routes 策略 — 域名未托管 Cloudflare 时不能直接 deploy 含 routes 的配置,需先去掉 routes、deploy 拿默认域名、再到 Dashboard 绑定(详见 references/tailoring.md Routes 配置策略表) 6. 安全优先 — .env* 必须进 .gitignore;对话 / 截图 / Slack / commit message 中出现过的 secret 一律视为已泄漏,立即 rotate(见 Phase 1.5 安全 baseline + Phase 4 Secret rotation 提醒) 7. 模板占位符必查 — mkfast-template 派生项目的 wrangler.jsonc 有 4 处模板默认值必改(name / routes.pattern / database_id / bucket_name)+ 1 处必关(logpush: true 在 Free/Pro plan 必报 code 10023)。见 references/components.md §0 占位符识别表;不查就 deploy 会写到模板原作者的 D1 或 deploy 失败
工作流(4 阶段)
Phase 1 — 自动评估(只读)
Step 1.0(必做,block 后续进度):对照 references/components.md §0 模板默认占位符识别表做 wrangler.jsonc + package.json diff。任何未替换的模板默认值——name="mkfast-template" / routes[0].pattern="demo.tanstarter.dev" / database_id="dc34f04a-3445-4b5c-bf61-c4ec2328e239" / r2.bucket_name="mkfast-template" / logpush: true——都直接 block 进 Phase 2。
这是本 skill 最重要的前置关:不查就 deploy 会(a)写到模板原作者的 D1 数据库;(b)deploy 失败报 code 10023(logpush 企业功能);(c)routes 冲突。所有这些都是 mkfast-template 派生项目首次部署几乎必踩的坑。
读取项目状态,识别"项目画像"。最少读这 5 个文件:
| 读什么 | 提取什么 |
|---|---|
wrangler.jsonc | name / routes / d1_databases / r2_buckets / send_email / 其他 binding |
package.json | name + 依赖中是否含 drizzle-orm / better-auth / stripe / creem / @beehiiv/sdk |
.env.example | 全部支持的变量分类(参照 references/components.md 检测信号表) |
.env.production(若存在) | 已配的变量 vs 未填(空字符串)的变量 |
.env.local(若存在) | CLOUDFLARE_ACCOUNT_ID + CLOUDFLARE_API_TOKEN 是否就绪 |
按 references/components.md 的 12 类组件逐一判断启用与否,生成"项目画像"输出(格式参照 templates/deploy-checklist.md):
- 项目形态:完整 SaaS / 裁剪版博客 / 中间形态
- 启用组件勾选表
- 环境变量缺口表
- 裁剪后的待执行步骤清单(步数)
Phase 1.5 — 安全 baseline(只读 + 必要时新建 .gitignore)
部署前必查 4 项,缺哪个补哪个,不让 secret 流出仓库是底线:
| 项 | 检查命令 | 不通过时的修复 |
|---|---|---|
.gitignore 存在且含 .env* | grep -E "\.env" .gitignore | 不存在则立即创建含 .env、.env.local、.env.*.local、.env.production、.dev.vars |
| .env.local 不在 git history | git log --all --full-history -- .env.local 输出为空 | 已被 commit 过 → 用 git filter-repo 清掉 + 立即 rotate 涉及的所有 secret |
wrangler.jsonc 无硬编码 secret | grep 不应见 sk_live_ / re_live_ / whsec_ 等 | 删硬编码,全走 wrangler secret put |
.env.production 无明文 production secret 准备 commit | `cat .gitignore | grep .env.production` |
裁剪版项目无 .gitignore 时(见过项目还没 git init 但已经写了 .env.local 的情况),先建 .gitignore 再继续 Phase 2。
Phase 2 — 决策点(AskUserQuestion)
基于 Phase 1 / 1.5 画像问用户,按情况选 1-5 题:
- 目标用户地域(强烈推荐先问):全球 / 仅海外 / 主要面向中国大陆 / 国内+海外都要
- 若选中国大陆 → ⚠️ 警告:Cloudflare 在中国大陆没有 PoP 节点(除非企业版 + 京东云合作),访问体验时通时不通;建议参考 `references/cn-access.md` 切换方案(EdgeOne / Vercel + 国内 CDN / 双部署)
- 域名状态(必问):
<domain>是否托管在 Cloudflare?三选项:已托管 / 未托管(先用默认 *.workers.dev)/ 还没买 - D1 / R2 资源(仅完整版):是否已在 Cloudflare 创建?如未创建,是否同意现在
wrangler d1 create/wrangler r2 bucket create? - 可选功能:Giscus / 分析 / Webhook / Crisp 是否本次启用?
- CI 自动化:是否同步配 GitHub Actions auto-deploy?
裁剪决策树详见 references/tailoring.md。
Phase 3 — 执行(按裁剪后清单)
Step 0 — Sanity(强烈推荐):
# 清掉过往 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推荐在 package.json 里加 alias 一次解决:```json
"deploy:cf": "set -a && . ./.env.local && set +a && pnpm run build && wrangler deploy"
```
之后用 pnpm deploy:cf 即可,避免每条 wrangler 命令都要 export。所有项目都要做: 1. pnpm wrangler whoami 验证登录(未登录时检查 .env.local 凭证或提示 wrangler login) 2. 检查 token 权限:按启用组件查 references/components.md 的 §API Token 权限完整矩阵(Workers Scripts / D1 / R2 / Workers Routes / Email / KV 等),缺权限直接到 Dashboard → My Profile → API Tokens 编辑现有 token 加权限(不用重建) 3. AskUserQuestion 最终确认 → pnpm run deploy
仅当检测到对应组件时做(按 references/components.md 详细步骤):
- (有 D1)
pnpm wrangler d1 create <name>→ 写回wrangler.jsonc的database_id→pnpm db:migrate:remote - (有 R2)
pnpm wrangler r2 bucket create <name>→ 写回bucket_name - (有 send_email) Cloudflare Dashboard 启用 Email Routing + 验证 from 邮箱
- (有 Better Auth)
pnpm dlx @better-auth/cli@latest secret生成 →wrangler secret put BETTER_AUTH_SECRET - (有服务端 secret)
pnpm wrangler secret bulk .env.production - (有支付) Stripe/Creem live key + price ID + webhook endpoint 配置
仅当用户在 Phase 2 选了 CI:
- 创建
.github/workflows/deploy.yml+ 提示去 GitHub Settings → Secrets 加CLOUDFLARE_ACCOUNT_ID/CLOUDFLARE_API_TOKEN/ 全部VITE_*
Phase 4 — 验证
# 在线验证(首次)
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>预期:
| 指标 | 首次 | 二次(warm) |
|---|---|---|
| HTTP code | 200 | 200 |
| size | > 几 KB(SSR 渲染成功) | 同首次或更小(命中缓存) |
| time | < 3s(含冷启动 + CDN 传播) | < 500ms(PoP 命中) |
附加检查:
- deploy 输出里
Total Upload: X KiB / gzip: Y KiB→Y < 3072(3 MB 免费计划上限) - Cloudflare Dashboard → Workers & Pages →
<worker-name>→ Logs → 无 runtime error - 自定义域名首次访问可能有 30s CDN 传播延迟
上线后 30 分钟观察清单:
| 检查项 | 命令 / 位置 | 预期 |
|---|---|---|
| 5xx 响应率 | Dashboard → Workers & Pages → <worker> → Logs → 筛 status >= 500 | 0 条 |
| 版本号匹配 | Dashboard → <worker> → Deployments | 最新 Version ID 与 deploy 输出一致 |
| Analytics 流量 | Dashboard → <worker> → Analytics(或第三方 RUM) | 有正常请求进入 |
| D1 表访问(若启用) | wrangler d1 execute <name> --remote --command "SELECT COUNT(*) FROM user" | 返回数字不报错 |
| R2 bucket 访问(若启用) | wrangler r2 object list <bucket> | 列表返回不报错 |
回滚方案:
# 列出历史版本
pnpm wrangler deployments list
# 回滚到上一版(不传 id)
pnpm wrangler rollback
# 回滚到指定版本
pnpm wrangler rollback <version-id>注意:
- D1 migrations 是 forward-only,schema 回滚需要手动写 down migration 并
wrangler d1 execute应用 - R2 无版本概念,按需从备份恢复(部署前做过
wrangler r2 object get备份才行) - Secret 回滚:
wrangler secret put <KEY>覆盖为旧值,或wrangler secret delete <KEY>清除
Secret rotation 提醒(重要): 如果本次部署过程中 token / API key / database secret 在以下任一渠道出现过:
- Claude / ChatGPT 等对话历史
- Slack / Lark / WeChat 消息
- 截图 / 录屏
- 代码注释 / commit message
→ 立即去 Cloudflare Dashboard / 第三方服务后台 roll(生成新值替代旧值)。token 字符串变了的,记得更新 .env.local 和 GitHub Secrets。
报错排查见 references/troubleshoot.md。
输出格式
每阶段结束后给用户简洁汇报,例:
Phase 1 项目画像输出(example)
## Phase 1 — 项目画像
**形态**:裁剪版博客
**启用组件**:无(纯 SSR + 博客)
**待执行步骤**:2 步(whoami + deploy)
**跳过**:D1 / R2 / Email / Auth / Pay / Secret bulk / Migrate最终 checklist(example)
✅ wrangler 登录(账户 zhihui)
✅ pnpm run deploy(gzip 471 KB / 17ms 启动)
✅ HTTP 200 验证通过
⚠️ 可选未启用:Giscus / Plausible工具文件
- `references/components.md` — mkfast-template 12 类组件检测信号 → 部署步骤映射表
- §0 模板默认占位符识别表(Phase 1 Step 1.0 必查)
- §API Token 权限完整矩阵(按启用组件勾权限的速查表)
- §没 .env.example 时如何反推 secret 列表(读 src/env/*.ts 反推)
- `references/tailoring.md` — 完整版 vs 裁剪版 vs 中间形态裁剪决策树 + 决策矩阵速查表
- §陷阱:
enable=false仍需保留 D1 binding(module-load-time 调用getDb()) - §澄清:
enable=false≠ tree-shake(enable 只控 UX,bundle 仍含 SDK)
- `references/troubleshoot.md` — 11 个常见报错 + 排查命令 + 修复(含 #10 Logpush / #11 already exists)
- `references/cn-access.md` — 中国大陆访问优化 4 档方案(Cloudflare 设置 / 图床 / EdgeOne 反代 / 双部署)
- `templates/deploy-checklist.md` — Phase 1 项目画像输出模板 + 最终汇报模板 + AskUserQuestion 模板
- `templates/stub-no-d1-r2.md` — 彻底关闭 D1/R2 的代码 stub 模板(4 文件改动可粘贴版本)
中国大陆访问优化方案
最后验证:2026-04(skill 编写时实测)
>
⚠️ 时效 disclaimer:本文涉及的 ICP 备案白名单域名列表、各家 CDN 产品策略、智能 DNS 服务商推荐、备案时长等信息变化较快。涉及决策前建议对照:
- 工信部域名信息备案管理系统 — 白名单域名实时查询
- 腾讯云 EdgeOne 产品文档 — 海外免备案 vs 国内版最新政策
- 阿里云备案指引 — 备案流程时长变动
>
如果本文档超过 6 个月未更新,且你正要做关键决策,请 Web 搜索 "Cloudflare 国内访问 2026" / "ICP 备案白名单 顶级域" 等 query 验证现状。
事实陈述
Cloudflare 在中国大陆没有自建 PoP 节点(除非企业版 + 京东云合作 + 域名 ICP 备案,门槛极高)。这意味着:
- Workers 访客的请求会绕到香港 / 新加坡 / 日本等海外节点
- 国内运营商对 Cloudflare CIDR 的策略时通时不通(移动 / 联通 / 电信表现各异)
- 单纯在 Cloudflare 后台调设置无法根本解决国内访问问题
如果你的目标用户主要在中国大陆,强烈建议在 Phase 2 决策时就换技术栈(不要选 Cloudflare Workers)。下面是几档优化方案,按成本从低到高排列。
---
🟢 方案 A:0 成本 — Cloudflare Dashboard 设置清单(缓解 ~10-30%)
只能缓解、不能解决。在 zone 后台逐项调:
必开
| 路径 | 设置 | 收益 |
|---|---|---|
| Speed → Optimization | 0-RTT Connection Resumption | 减少 TLS 握手 RTT |
| Speed → Optimization → Network | HTTP/3 (with QUIC) | 国内移动网络下偶尔比 H/2 稳 |
| Caching → Configuration | Cache Level: Standard、Browser Cache TTL: 4h | 让浏览器多缓存 |
| Caching → Tiered Cache | Smart Tiered Caching | 跨 PoP 间回源减少 |
| SSL/TLS → Edge Certificates | Min TLS Version: 1.2、Automatic HTTPS Rewrites | TLS 协商更快 |
| Network | gRPC、WebSockets | 默认开 |
Pro/Business plan 才有
| 路径 | 设置 | 收益 |
|---|---|---|
| Speed → Image Optimization | Polish: Lossy + WebP | 图片减重 30-50% |
Cache Rules(手动加)
给 hash 静态资源加 Cache Everything + Edge TTL: 1 month,避免回源 worker:
URL Path matches: /assets/*
URL Path matches: /_build/*
URL Path matches: /favicon.*
URL Path matches: /og.*⚠️ 不要开
Rocket Loader— TanStack hydration 会出诡异 bugAuto Minify— 已被 Cloudflare 弃用(Tailwind/Vite 自己已 minify)Always Online— Workers 不支持
---
🟡 方案 B:低成本 — 图片换国内可达 CDN(缓解最常见痛点)
如果痛点是图片加载慢(unsplash / Cloudflare R2 都在境外),换国内图床能立竿见影。
| 选项 | 备案 | 速度 | 成本 |
|---|---|---|---|
| 七牛云 | 要 | 快 | 10 GB 免费/月 |
| 又拍云 | 要 | 快 | 类似 |
| 阿里云 OSS + CDN | 要 | 快 | 流量按需 |
| Bunny CDN | 不要 | 中等(部分时段可用) | $0.01/GB |
| GitHub + jsDelivr CDN | 不要 | 时通时不通 | 免费 |
接入方式:把 markdown 里 images.unsplash.com 的 URL 替换成 CDN URL。可以写脚本批量下载 → 上传 → 替换。
---
🟠 方案 C:中等成本 — 备案域名 + 国内 CDN 反代(真正解)
唯一能彻底解决的方案:
访客(国内)
↓ DNS 解析
腾讯 EdgeOne / 阿里云 CDN(国内节点,要 ICP 备案)
↓ 回源
<your-domain>(Cloudflare Workers)前置条件
1. 域名能备案:工信部 ICP 备案白名单只有少数顶级域:
- ✅ 能备案:
.com、.cn、.net、.com.cn、.org、.gov.cn、.app、.dev、.xyz(部分)、.top、.shop(部分) - ❌ 不能备案:
.photos、.gallery、.io、.ai、.dev(争议)、.tech、大部分新顶级域 - 不在白名单意味着无论如何无法接入国内 CDN,必须额外买一个能备案的域名做反代入口
2. 备案要主体:
- 个人:身份证 + 国内手机号 + 国内住址
- 企业:营业执照 + 法人 + 公章
- 必须有国内云厂商的服务器(阿里云/腾讯云"备案专用云",约 ¥30-100,备案完可释放)
3. 耗时:首次备案 7-20 个工作日,期间不可访问
推荐产品
- 腾讯云 EdgeOne:直接对标 Cloudflare(域名 + DNS + CDN + Edge Function)。海外节点免备案,国内节点要备案。Edge Functions 类似 Workers 但 API 不兼容(需要重新部署)。
- 阿里云 CDN + ESA:传统 CDN + 边缘安全加速,纯反代用足够。
- 百度智能云 / 火山引擎:备选。
接入步骤(以腾讯云 EdgeOne 为例)
1. 备案完成 2. EdgeOne 控制台 → 添加站点(域名 + 接入方式) 3. 修改域名 NS(从 Cloudflare 转走)→ 指向 EdgeOne 提供的 NS 4. 配置回源:源站类型 IP/域名 → 填 <your-cf-domain> 或 Cloudflare Workers URL 5. 等 NS 生效(24h) 6. 国内访客自动走 EdgeOne 国内节点,海外走 EdgeOne 海外节点
---
🔴 方案 D:高成本最稳 — 双部署 + 智能 DNS
把站点同时部署到 Cloudflare(境外)+ Vercel/EdgeOne(境内),用第三方智能 DNS 按访客地域分流。
访客
↓ DNS(DNSPod 智能解析 / 阿里云 DNS)
├─ 境内 → Vercel / EdgeOne 海外免备案版
└─ 境外 → Cloudflare Workers关键约束
- DNS 必须用第三方智能解析(Cloudflare DNS 不支持地域分流)
- DNSPod(腾讯)— 国内最成熟
- 阿里云 DNS
- NS1(海外,国内访问慢)
- Vercel 在国内走日本/新加坡节点,Hobby plan 没有中国节点;比 Cloudflare 略好但仍不稳
- EdgeOne 海外免备案版 节点在香港/新加坡,无需备案,但相比国内备案版有差距
- 要把 TanStack Start 适配多个 preset:默认是 Cloudflare preset,需要新增 Vercel preset;D1 / R2 在 Vercel 上没等价物(需换成 Postgres / S3)
---
我的具体建议(按 user 类型)
| User 类型 | 推荐方案 |
|---|---|
| 个人项目,学习用 | A 方案 + 不折腾国内访问 |
| 作品集 / 技术博客(80% 海外读者) | A + B 方案 |
| 面向中国大陆用户的产品 | 不要用 Cloudflare Workers,从一开始就选 Vercel 或腾讯 EdgeOne / 阿里云函数计算 + ESA |
| 跨境电商 / 全球化 SaaS | C 或 D 方案,备案域名 + 双部署 |
---
Phase 2 决策树
skill 在 Phase 2 问过"目标用户地域"后,按答案路由:
全球 / 仅海外 → 继续 Cloudflare Workers 部署
中国大陆 → 强烈警告 + 推荐换栈(Vercel / EdgeOne)
→ 如果坚持用 Cloudflare:A + B 方案,告知性能预期
国内 + 海外 → C 或 D 方案,先备案再继续mkfast-template 组件检测信号 → 部署步骤映射
用途:Phase 1 评估时,根据下表检测每类组件是否启用,生成裁剪后的步骤清单。
---
§0 模板默认占位符识别表(必查,避免写到模板原作者的资源)
mkfast-template 派生项目第一次部署时,wrangler.jsonc 通常还是模板默认值。直接 deploy 会出现两类灾难:(a) 用了模板原作者的 D1 database_id → 写入或迁移别人的数据库;(b) 用了模板示例域名 demo.tanstarter.dev → routes 冲突 deploy 失败。
wrangler.jsonc 必查项
| 字段 | 模板默认值(必改) | 改成什么 |
|---|---|---|
name | "mkfast-template" | 你的项目名(小写、连字符)e.g. "gpt-images" |
routes[0].pattern | "demo.tanstarter.dev" | 你的真实域名 e.g. "yoursite.com" |
d1_databases[0].database_name | "mkfast-template" | 项目名 + -db e.g. "gpt-images-db" |
d1_databases[0].database_id | "dc34f04a-3445-4b5c-bf61-c4ec2328e239" | 本地 placeholder 后用 `wrangler d1 create` 拿真实 uuid 写回 |
r2_buckets[0].bucket_name | "mkfast-template" | 项目名 + -files e.g. "gpt-images-files" |
logpush | true | 改 `false`(Logpush 是 Business/Enterprise 功能,Free/Pro plan deploy 会报 code 10023) |
Phase 1 一键检查命令
grep -E '"(name|pattern|database_name|database_id|bucket_name|logpush)"' wrangler.jsonc如果看到任何 mkfast-template / demo.tanstarter.dev / dc34f04a-... / logpush": true —— 必须先全改完才能进 Phase 3。
package.json 必查
| 字段 | 模板默认 | 改成什么 |
|---|---|---|
name | "mkfast-template" | 你的项目名(影响 npm publish;私有项目无所谓但建议改) |
资源命名建议
为避免后续多项目混乱,统一命名规范 <project>-{db,files,kv,...}:
- D1 →
<project>-db(如gpt-images-db) - R2 →
<project>-files(如gpt-images-files) - KV →
<project>-cache - Workers →
<project>(不加后缀)
---
§API Token 权限完整矩阵
mkfast-template 一个 token 通常需要覆盖多个组件。强烈推荐一次性把所有可能用到的权限勾全,避免后续启用功能再改 token(每改一次都要重新邮箱验证码)。
按启用组件勾权限
| 启用组件 | 范围 | 资源 | 权限 |
|---|---|---|---|
| 必备(任何 Worker 都要) | Account | Workers Scripts | Edit |
| 自定义域名(custom_domain) | Zone | Workers Routes | Edit |
| D1 操作 | Account | D1 | Edit |
| R2 操作 | Account | Workers R2 Storage | Edit |
| Email Routing | Account | Email Routing Addresses | Edit |
| Email Routing | Account | Email Workers | Edit |
| Workers AI | Account | Workers AI | Edit |
| Pages | Account | Cloudflare Pages | Edit |
| KV 操作 | Account | Workers KV Storage | Edit |
| Hyperdrive | Account | Hyperdrive | Edit |
| Queues | Account | Queues | Edit |
Zone Resources 范围
Include → Specific zone → 选你的域名(如 gptimages.photos)。不要选 All zones 除非你管理多个 zone。
创建 token 的入口
Cloudflare Dashboard → 右上角头像 → My Profile → API Tokens → Create Token → Custom Token(不要用 "Edit Cloudflare Workers" 模板,它默认不含 D1/R2/Email)
权限不足的报错
✘ ERROR Authentication error [code: 10000]修复:到 token 编辑页加缺失权限,保存后 token 字符串不变,不需要重写 .env.local,重跑命令即可。
---
§没 .env.example 时如何反推 secret 列表
裁剪版项目可能把 .env.example 删了。退路是读 @t3-oss/env-core 的 schema 反推:
# 服务端 secret(运行时,要 wrangler secret put)
cat src/env/server.ts
# 客户端变量(编译时,写 .env.production / GitHub Secrets)
cat src/env/client.tscreateEnv({ server: { ... } }) 里 z.object({...}) 的所有 keys 就是完整变量集。判断必填 vs 可选:
z.string()/z.url()— 必填z.string().optional()— 可选z.string().default('xxx')— 有默认值,不配也能跑(但生产建议显式配,default 多是占位)
实例(gpt-image2-collections-site):
- 必填带 default:
VITE_BASE_URL、BETTER_AUTH_SECRET - 可选:
GOOGLE_CLIENT_ID/SECRET、RESEND_API_KEY、BEEHIIV_*、STRIPE_*、DISCORD_WEBHOOK_URL、FEISHU_WEBHOOK_URL
重要:VITE_*是 build-time 变量,只能写.env.local/.env.production/ GitHub Secrets,不能用 wrangler secret(运行时才生效,build 时已经晚了)。
---
检测优先级
1. wrangler.jsonc binding 是金标准 — 配了 binding 但代码不用 → 仍按启用处理(避免漏部署资源) 2. package.json 依赖是辅助信号 — 依赖在但 binding 没配 → 按未启用处理(说明用户主动剥离了) 3. .env.production 变量是最终判定 — 变量为空字符串 = 未启用;有值 = 启用
12 类组件
1. D1 Database
| 项 | 值 |
|---|---|
| 检测信号 | wrangler.jsonc 含 d1_databases binding 且 package.json 含 drizzle-orm |
| 完整版默认 | ✅ 启用 |
| 部署步骤 | 1. pnpm wrangler d1 create <database_name> → 拿到 database_id<br>2. database_id 写回 wrangler.jsonc 的 d1_databases[0].database_id<br>3. pnpm db:migrate:remote(底层 wrangler d1 migrations apply <name> --remote) |
| 跳过条件 | 无 D1 binding 或无 drizzle 依赖 |
2. R2 Bucket
| 项 | 值 |
|---|---|
| 检测信号 | wrangler.jsonc 含 r2_buckets binding |
| 完整版默认 | ✅ 启用 |
| 部署步骤 | 1. pnpm wrangler r2 bucket create <bucket_name>(已存在会报错但不影响)<br>2. bucket_name 已配则跳过修改 |
| 跳过条件 | 无 R2 binding |
3. Cloudflare Email Service
| 项 | 值 |
|---|---|
| 检测信号 | wrangler.jsonc 含 send_email binding 或 src/mail/provider/cloudflare.ts 文件存在 |
| 完整版默认 | ✅ 启用 |
| 部署步骤 | 1. Cloudflare Dashboard → 域名 → Email → Email Routing → Enable<br>2. 添加并验证 from 邮箱地址<br>3. 代码无需改(mkfast 已封装) |
| 跳过条件 | 无 send_email binding |
4. Better Auth
| 项 | 值 |
|---|---|
| 检测信号 | package.json 含 better-auth 依赖 或 .env.example 含 BETTER_AUTH_SECRET |
| 完整版默认 | ✅ 启用 |
| 部署步骤 | 1. 生成 secret:pnpm dlx @better-auth/cli@latest secret<br>2. wrangler secret put BETTER_AUTH_SECRET(粘贴上一步的 secret)<br>3. (Google OAuth)回调 URL 配 https://<domain>/api/auth/callback/google |
| 跳过条件 | 无 better-auth 依赖 |
5. Google OAuth
| 项 | 值 |
|---|---|
| 前置 | 仅在 Better Auth 启用时有意义 |
| 检测信号 | .env.production 的 GOOGLE_CLIENT_ID 和 GOOGLE_CLIENT_SECRET 都非空 |
| 部署步骤 | 1. Google Cloud Console → APIs & Services → Credentials → 创建 OAuth 2.0 Client → 拿 Client ID + Secret<br>2. Authorized redirect URIs 加 https://<domain>/api/auth/callback/google<br>3. 写到 .env.production,后续 secret bulk 自动推 |
| 跳过条件 | .env 里 GOOGLE_* 为空 |
6. Stripe Payment
| 项 | 值 |
|---|---|
| 检测信号 | package.json 含 stripe 依赖 且 .env.production 的 VITE_PAYMENT_PROVIDER='stripe' |
| 完整版默认 | ⚠️ 二选一(stripe 或 creem) |
| 部署步骤 | 1. Stripe Dashboard → API keys → 拿 live secret(生产必须用 live,不要 test)<br>2. Stripe Dashboard → Products → 创建 Pro Monthly / Yearly / Lifetime → 拿 price ID<br>3. .env.production 填 STRIPE_SECRET_KEY + 三个 VITE_STRIPE_PRICE_*<br>4. Webhook:Dashboard → Webhooks → 添加 https://<domain>/api/webhooks/stripe → 拿 signing secret → 填 STRIPE_WEBHOOK_SECRET |
| 跳过条件 | VITE_PAYMENT_PROVIDER 不是 'stripe' 或无 stripe 依赖 |
7. Creem Payment
| 项 | 值 |
|---|---|
| 检测信号 | package.json 含 creem 依赖 且 VITE_PAYMENT_PROVIDER='creem' |
| 部署步骤 | 类似 Stripe,但走 Creem Dashboard;生产环境记得把 `CREEM_DEBUG` 改为 `'false'` |
| 跳过条件 | 同 Stripe(互斥) |
8. Beehiiv Newsletter
| 项 | 值 |
|---|---|
| 检测信号 | package.json 含 @beehiiv/sdk 依赖 |
| 部署步骤 | 1. Beehiiv Dashboard → Settings → API → 创建 API Key<br>2. 拿 publication ID(pub_xxx)<br>3. .env.production 填两个值,secret bulk 推 |
| 跳过条件 | 无 @beehiiv/sdk 依赖 |
9. Discord / Feishu Webhook(联系表单通知)
| 项 | 值 |
|---|---|
| 检测信号 | .env.production 的 DISCORD_WEBHOOK_URL 或 FEISHU_WEBHOOK_URL 非空 |
| 部署步骤 | 1. Discord:服务器设置 → 集成 → Webhook → 创建 → 复制 URL<br>2. Feishu:群机器人 → 自定义机器人 → 复制 webhook URL(注意:飞书 webhook 必须配置签名校验或 IP 白名单)<br>3. 填入 .env.production |
| 跳过条件 | 用户不需要联系表单通知 |
10. Crisp Chat
| 项 | 值 |
|---|---|
| 检测信号 | .env.production 的 VITE_CRISP_WEBSITE_ID 非空 |
| 部署步骤 | 1. crisp.chat 注册 → 创建 Website → 拿 Website ID(UUID 格式)<br>2. 填 .env.production,build 时打入客户端 |
| 跳过条件 | 用户不需要客服聊天 |
11. Analytics(Plausible / GA / Umami / Clarity 任选)
| 项 | 值 |
|---|---|
| 检测信号 | .env.production 任一非空:VITE_PLAUSIBLE_SCRIPT / VITE_GOOGLE_ANALYTICS_ID / VITE_UMAMI_* / VITE_CLARITY_PROJECT_ID |
| 部署步骤 | 选一种服务注册 → 拿 script URL / tracking ID → 填 .env.production |
| 推荐 | Plausible(隐私友好、国内可访问)> Umami(自建)> GA4 > Clarity(仅热力图) |
| 跳过条件 | 全部为空 = 不接入分析 |
12. Affiliate(Affonso / PromoteKit 任选)
| 项 | 值 |
|---|---|
| 检测信号 | VITE_AFFILIATE_AFFONSO_ID 或 VITE_AFFILIATE_PROMOTEKIT_ID 非空 |
| 部署步骤 | 注册对应平台 → 拿 ID → 填 .env.production |
| 跳过条件 | 不做分销 |
---
关键规则:VITE_* vs 服务端 secret
部署时两种变量处理方式不同:
- *`VITE_` 开头 = 编译时**变量,Vite build 阶段读取打入客户端 bundle
→ 写在 `.env.production` 即可,不要用 `wrangler secret`(secret 是运行时,build 时 wrangler 还没启动)
- *非 `VITE_
开头**(如STRIPE_SECRET_KEY/BETTER_AUTH_SECRET/CLOUDFLARE_DATABASE_ID`)= 运行时变量
→ 必须用 `wrangler secret put` 或 `wrangler secret bulk` 推到 Cloudflare
判断 secret bulk 是否需要执行
grep -vE "^#|^$|^VITE_|^CLOUDFLARE_(ACCOUNT_ID|API_TOKEN)=" .env.production | grep -v "^$"- 输出为空 → 不需要
secret bulk(裁剪版常见) - 输出有内容 → 必须执行
pnpm wrangler secret bulk .env.production(bulk 会推全部变量,包括VITE_*但无害)
注意排除 CLOUDFLARE_ACCOUNT_ID / CLOUDFLARE_API_TOKEN —— 这两个是 wrangler CLI 自身用的,不是 Worker 运行时需要的。
---
数量预估
按形态预估 Phase 3 步骤数:
- 完整版 SaaS(1-7 全启用,8-12 选启用)→ 8-11 步
- 裁剪版博客(zhihui-site 模式:全部跳过)→ 2 步(whoami + deploy)
- 中间形态(如博客 + Better Auth)→ 4-5 步
部署步骤裁剪决策树
三种典型形态
形态 A:完整版 SaaS(默认 mkfast-template)
特征:D1 + R2 + Email + Better Auth + Stripe/Creem + Beehiiv + Webhook + Crisp + Analytics 全启用
部署步骤(约 10 步): 1. pnpm wrangler whoami 2. pnpm wrangler d1 create <name> + 写回 database_id 3. pnpm wrangler r2 bucket create <name> 4. Cloudflare Dashboard 启用 Email Routing + 验证 from 邮箱 5. pnpm dlx @better-auth/cli@latest secret → wrangler secret put BETTER_AUTH_SECRET 6. 配 Stripe/Creem live key + price ID + webhook endpoint 7. 配 Google OAuth 回调(Google Cloud Console) 8. pnpm wrangler secret bulk .env.production 9. pnpm db:migrate:remote 10. pnpm run deploy 11. (可选)配 GitHub Actions CI
形态 B:裁剪版博客(zhihui-site 模式)
特征:
package.json没有drizzle-orm/better-auth/stripe/creem/@beehiiv/sdkwrangler.jsonc没有d1_databases/r2_buckets/send_email.env.production只有VITE_BASE_URL(可选 Giscus / 分析)
部署步骤(精简到 2 步): 1. pnpm wrangler whoami 2. pnpm run deploy
绝对不做(容易踩的坑):
- ❌
wrangler d1 create—— 无 D1 binding,跑了也无意义 - ❌
wrangler r2 bucket create—— 无 R2 binding - ❌
wrangler secret bulk—— 无服务端 secret,即使跑了也只会推VITE_*没用 - ❌
db:migrate:remote—— 无 drizzle 配置,会找不到 migrations 目录报错(mkfast 裁剪版连drizzle.config.ts都没有)
形态 C:中间形态(自定义子集)
按 references/components.md 的检测信号逐个判断。例如:
- 项目有 D1 但没接 Better Auth → 部署 D1 + migrate,跳过 BETTER_AUTH_SECRET 生成
- 项目有 R2 但没接 Stripe → 部署 R2,跳过 Stripe 配置和 webhook
- 项目有 Better Auth 但用外部 Postgres(Hyperdrive)→ 跳过 D1,但配 BETTER_AUTH_SECRET 和 Hyperdrive
---
陷阱:websiteConfig.auth.enable=false 不能省 D1 binding
现象
新手以为"既然关了 auth,D1 也用不上,干脆 wrangler.jsonc 里删掉 d1_databases binding 完事"。 实际 deploy 后 worker 启动报错(runtime error),首屏直接 500。
根因
src/auth/auth.ts 在 module top-level 调用 getDb():
// src/auth/auth.ts line ~19
export const auth = betterAuth({
database: drizzleAdapter(getDb(), { provider: 'sqlite' }), // ← getDb() 立即执行
...
});而 src/db/index.ts 的 getDb() 读 env.DB:
export function getDb() {
return drizzle(env.DB, { schema }); // env.DB undefined → drizzle 调用崩
}只要 worker bundle 里任何一个文件 import 了 auth(如 /api/auth/$.ts 路由文件),module load 时就会触发 getDb() → env.DB undefined → 启动失败。
三种处理方案
| 方案 | 改动量 | 适用场景 |
|---|---|---|
| A. 创建空 D1+R2 资源(推荐) | wrangler.jsonc 不动;建空资源(免费) | 未来可能启用 auth/payment/storage |
| B. 用 stub 代码彻底关闭 | 改 4 个文件(见 templates/stub-no-d1-r2.md) | 长期不启用、追求最小依赖 |
| C. 干脆开启 auth | websiteConfig.auth.enable: true + 配 BETTER_AUTH_SECRET | 反正都需要 D1,正经启用 auth |
默认推荐 A(5 分钟完成,免费,无代码改动): 1. wrangler d1 create <project>-db → 拿 uuid → 写回 wrangler.jsonc 2. wrangler r2 bucket create <project>-files 3. pnpm db:migrate:remote(auth schema 上 D1,即使现在不用,建好不浪费) 4. deploy
B 方案完整代码模板 → templates/stub-no-d1-r2.md
---
澄清:enable: false ≠ tree-shake
mkfast 的 websiteConfig.*.enable 控制的是 UX 层(navbar 是否渲染 user button、是否 mount auth route 等),不会从 worker bundle 里删除对应 SDK。
实测体积对比(gpt-image2-collections-site,2026-04 实测)
| 配置 | gzip 体积 | 减包来源 |
|---|---|---|
| 完整 SaaS(全 enable: true) | ~1.7 MB | baseline |
| 关 payment.enable | ~1.62 MB | 仅 -80 KB(stripe SDK 仍在 bundle,238 KB worker chunk) |
| 关 auth.enable | ~1.55 MB | 仅 -150 KB(better-auth 仍在 bundle) |
| 关全部 enable | ~1.66 MB(实测) | 几乎无变化 |
真正减包的方法
要让对应 SDK 不进 bundle,必须从代码层面删 import:
| SDK | 需删的文件 |
|---|---|
| Stripe (~240 KB gzip) | src/payment/、src/routes/api/webhooks/stripe.ts、src/routes/(pages)/pricing.tsx |
| Better Auth (~150 KB) | src/auth/、src/middlewares/{auth,admin}-middleware.ts、src/routes/api/auth/$.ts、src/routes/auth/、src/routes/settings/、src/routes/admin/ |
| Resend (~50 KB) | src/mail/、src/newsletter/provider/resend.ts |
| Beehiiv | src/newsletter/provider/beehiiv.ts |
| Stripe webhook | src/routes/api/webhooks/stripe.ts |
取舍:开发阶段用 enable flag 快速切换是 OK 的;如果生产长期不启用,建议彻底删除对应代码(用 templates/stub-no-d1-r2.md 作为 auth 的彻底删除参考模板)。
决策矩阵(快速查表)
| 项目特征 | D1 create | R2 create | Auth secret | Pay 配置 | Secret bulk | DB Migrate | |
|---|---|---|---|---|---|---|---|
| 完整 SaaS | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| 博客 + 评论(zhihui) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| 博客 + 联系表单(用 webhook) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| 文档站 + Auth | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ |
| SaaS + Postgres(Hyperdrive) | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| 内容订阅(只用 Beehiiv) | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
| Markdown 博客 + 图片上传 R2 | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
Routes 配置策略
| 域名状态 | wrangler.jsonc routes 配置 | 部署策略 |
|---|---|---|
| 已托管 Cloudflare | pattern: "your-domain.com", custom_domain: true | 直接 deploy |
| 未托管 Cloudflare | 临时去掉 routes 字段 | deploy → 拿 *.workers.dev 默认域名 → Cloudflare Dashboard 手动绑域名 |
| 还没买域名 | 不配 routes | deploy → 用 *.workers.dev 默认域名上线 → 之后买域名再绑 |
注意:custom_domain: true 模式要求 token 有 Zone > Workers Routes > Edit 权限,无此权限会在 deploy 时报 10001 错误。
CI 自动化决策
| 用户需求 | workflow 触发 | 必备 GitHub Secrets |
|---|---|---|
| 不要 CI | 不创建 yml | / |
| 手动触发(GitHub 网页点 button) | workflow_dispatch | CLOUDFLARE_ACCOUNT_ID / CLOUDFLARE_API_TOKEN / 全部 VITE_* |
| push 自动 | push: branches: [main] + workflow_dispatch(推荐保留手动入口作为兜底) | 同上 |
| PR 预览 | pull_request | 同上 + 改 deploy 命令为 wrangler deploy --env preview |
模板 yml(最小可工作):
name: deploy
on:
workflow_dispatch:
# push: { branches: [main] } # 取消注释以启用自动触发
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm run build
env:
VITE_BASE_URL: ${{ secrets.VITE_BASE_URL }}
# 项目里所有其他 VITE_* 也要加
- run: pnpm wrangler deploy
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}关键提醒
secret bulk在 CI 里不需要重复执行 —— secret 是 Cloudflare 端持久化的,CI 部署的 Worker 自动继承之前 put 进去的 secret- 但
VITE_*每次 build 都需要从 GitHub Secrets 注入,因为它们是编译时打入 bundle 的 - 这就是 mkfast 文档强调"VITE_ 必须加到 GitHub Secrets"的原因
mkfast-deploy 常见报错排查
目录
| # | 报错 | 关键词 |
|---|---|---|
| 1 | Authentication error 10000 / 10001 | token 权限不足 |
| 2 | 域名路由冲突 | 已被其他 Worker 占用 |
| 3 | Worker 大小超限(3 MB / 10 MB) | Script size exceeds the limit |
| 4 | Binding undefined(运行时报错) | env.DB / env.BUCKET undefined |
| 5 | D1 Migrations 不工作 | No migrations to apply / Already applied |
| 6 | Secret bulk 失败 | Invalid JSON / CRLF |
| 7 | CDN 缓存 / DNS 传播导致旧版本 | 404 / 旧版本 |
| 8 | compatibility_date 过期警告 | >90 days |
| 9 | nodejs_compat flag 缺失 | Cannot find module 'node:crypto' |
| 10 | Logpush enable failed (code 10023) | Free/Pro 不支持 |
| 11 | d1 / r2 create 报 "already exists" | race condition 或重复创建 |
| — | 排查流程速查 | 按症状分流 |
---
1. Authentication error 10000 / 10001
症状:
✘ [ERROR] A request to the Cloudflare API failed.
✘ Authentication error [code: 10000]原因:Cloudflare API Token 权限不足或失效。
排查:
pnpm wrangler whoami # 看认证状态 + 当前 account然后到 API Tokens 页 检查 token 权限。
完整权限矩阵 → 见 references/components.md 的 §API Token 权限完整矩阵(按启用组件勾,含 Email Routing / Workers AI / KV / Hyperdrive / Queues 等)。
修复:在 token 编辑页加缺失的权限,保存,重跑命令。token 字符串不变,无需更新 .env.local。
注意:whoami 可能能过但 D1/R2 API 仍然 10000 —— 因为 whoami 只校验 Account 读权限,D1/R2 是单独的资源权限。每次新启用一类资源都要回 token 加权限。
2. 域名路由冲突
症状:
✘ Custom domain "your-domain.com" is already routed to a different Worker.原因:同一个域名 / 路由被另一个 Worker 占用(常见:之前部署过同名 Worker 又改名)。
修复:
- Cloudflare Dashboard → Workers & Pages → 找到旧 Worker → Settings → Domains & Routes → 删除该域名
- 或改用子域:
pattern: "app.your-domain.com"
3. Worker 大小超限(3 MB / 10 MB)
症状:
✘ Script size exceeds the limit of 3145728 bytes (gzip)原因:Workers 免费计划 3 MB gzip,付费 10 MB。常见超限:
- 引入大体积依赖(
puppeteer、整个aws-sdk、未 tree-shake 的lodash) - 把字体 / 图片打到 server bundle(应放
public/走 assets 静态托管) - 未启用 minify
排查:
ls -lah dist/server/assets/ | sort -k5 -h | tail -20找最大的几个 chunk,定位来源。
修复:
- 移除大依赖,换轻量替代(如
lodash→lodash-es+ tree-shake) - 字体改用 CDN 或
public/静态托管 - 升级到付费计划($5/月,10 MB 上限)
4. Binding undefined(运行时报错)
症状:
TypeError: Cannot read properties of undefined (reading 'prepare') # D1
TypeError: env.BUCKET is undefined # R2原因:
wrangler.jsonc没配 binding,但代码里在用- binding name 不一致(配置写
"binding": "DB",代码用env.DATABASE) wrangler types没重跑,TS 类型过时但实际运行时也缺
修复: 1. 检查 wrangler.jsonc 的 binding name 与代码完全一致 2. 重跑 pnpm cf-typegen 生成 worker-configuration.d.ts 3. 重新 pnpm run deploy
5. D1 Migrations 不工作
症状:
No migrations to apply但本地src/db/migrations/有.sql文件Migration 0001 already applied但表实际不存在
原因:
wrangler.jsonc的migrations_dir指错路径- 远程 D1 的
d1_migrations表记录与实际状态不一致 - 误用
wrangler d1 execute跑过 SQL,导致两套状态
修复:
# 查看远程实际表
pnpm wrangler d1 execute <name> --remote \
--command "SELECT name FROM sqlite_master WHERE type='table'"
# 查看 migration 记录
pnpm wrangler d1 execute <name> --remote \
--command "SELECT * FROM d1_migrations"
# 重置 migration 记录(危险,仅在确认空库时跑)
pnpm wrangler d1 execute <name> --remote \
--command "DELETE FROM d1_migrations"如果要从备份恢复:pnpm wrangler d1 export <name> --remote --output backup.sql 先备份。
6. Secret bulk 失败
症状:
✘ Failed to bulk update secrets
✘ Invalid JSON原因:
.env.production含特殊字符(=、引号、换行)未转义- 文件含 BOM 或 Windows 换行符(
\r\n)
修复:
# 转换换行符
dos2unix .env.production
# 单个 put 调试,定位是哪个 key 出问题
echo "value" | pnpm wrangler secret put KEY_NAME
# 验证文件格式
file .env.production # 应显示 "ASCII text" 不带 "with CRLF"7. CDN 缓存 / DNS 传播导致旧版本
症状:deploy 输出成功,但访问域名看到旧版本 / 404 / 默认欢迎页。
原因:Cloudflare 边缘缓存 + DNS 传播延迟。
修复:
- 通常 30s 内自动生效,先等等
- 仍未生效:Dashboard → Caching → Purge Everything
- 浏览器开 DevTools → Network → 勾选 Disable cache + 强刷(Cmd+Shift+R)
- 用
curl验证(绕过浏览器缓存):
curl -I https://<domain>8. compatibility_date 过期警告
症状:
⚠ Compatibility date is more than 90 days in the past原因:wrangler.jsonc 的 compatibility_date 过老,错过了新 runtime feature。
修复: 1. 查 compatibility dates 列表 2. 选最新日期(一般用今天日期)更新 wrangler.jsonc 的 compatibility_date 3. 重新 deploy
注意:升级 compatibility_date 偶尔会引入 breaking change,先在 staging 环境测试。
9. nodejs_compat flag 缺失
症状:
Error: Cannot find module 'node:crypto'
ReferenceError: Buffer is not defined原因:Worker 默认不启用 Node.js 兼容层,需要显式开启。
修复:wrangler.jsonc 加:
"compatibility_flags": [
"nodejs_compat",
"nodejs_compat_populate_process_env"
]mkfast-template 默认已配。如果是从零项目或修改了配置,要手动加回。
10. Logpush enable failed (code 10023)
症状:deploy 上传成功后,最后一步报:
✘ [ERROR] A request to the Cloudflare API ... failed.
You do not have access to use Logpush.
Please ensure it is enabled. If you are an Enterprise user,
reach out to your account team. [code: 10023]原因:mkfast-template 的 wrangler.jsonc 默认 "logpush": true,Logpush 是 Business / Enterprise plan 独享。Workers Free / Pro plan 都不支持。
修复:wrangler.jsonc:
// 改成 false(或删除整行)
"logpush": falseobservability.logs 仍然可用(免费 plan 支持),无功能影响。
踩坑频率:mkfast-template 派生项目第一次部署几乎必踩,因为大部分用户都不在企业版。建议项目模板源头修复 logpush: false 默认值。
11. d1 / r2 create 报 "already exists"
症状:
✘ ERROR A database with that name already exists
# 或
✘ ERROR A bucket with that name already exists常见原因:
- 上次 create 在 API auth 失败前已经落库了(race condition:先创建再校验权限,权限失败但资源已建)
- 之前自己手动创建过同名资源忘了
修复:先 list 看看 uuid,写回 wrangler.jsonc 即可:
# D1
pnpm wrangler d1 list
# 找到 name=<your-name> 那行,复制 uuid → 写入 wrangler.jsonc d1_databases[0].database_id
# R2
pnpm wrangler r2 bucket list
# bucket 没有 uuid,name 唯一即可,确认 wrangler.jsonc 的 bucket_name 一致确认资源是空的(重要):
# D1:看表数量,0 就是空的
pnpm wrangler d1 execute <name> --remote \
--command "SELECT name FROM sqlite_master WHERE type='table'"
# R2:看 object 数量
pnpm wrangler r2 object list <bucket-name>如果资源里有别人的数据(特别是 database_id 一致但你不记得创建过),立即停下排查——可能误用了模板原作者的资源。换名重建(<project>-db-v2 之类)。
---
排查流程速查
遇到 deploy 失败时按这个顺序查:
1. wrangler whoami → 排除登录问题
2. 看错误码 10xxx → 查权限矩阵(components.md 的 §API Token 矩阵)
3. 看错误码 10023 → Logpush 企业功能(#10)
4. 看输出大小 → 查包超限(#3)
5. d1/r2 已存在? → 先 list 拿 uuid(#11)
6. 访问域名能打开吗?
- 不能 → DNS / CDN 问题(#7)/ 域名冲突(#2)
- 能但 500 报错 → binding 问题(#4)/ secret 缺失(#6)/ migration 缺失(#5)/ enable=false 但删了 binding(→ tailoring.md 陷阱章节)
7. compatibility_date 警告 → 更新(#8)
8. node 模块报错 → flag 缺失(#9)部署清单输出模板
供 SKILL.md 各 Phase 引用,保持输出格式一致。
---
Phase 1 评估输出模板
````markdown
项目画像
项目名:<package.json name> 模板形态:完整 SaaS / 裁剪版博客 / 中间形态(<具体说明>) 部署目标:<wrangler.jsonc routes.pattern 或 *.workers.dev>
已启用组件
| 组件 | 状态 | 说明 |
|---|---|---|
| D1 Database | ✅ / ❌ | <database_name> |
| R2 Bucket | ✅ / ❌ | <bucket_name> |
| Cloudflare Email | ✅ / ❌ | from: <email> |
| Better Auth | ✅ / ❌ | (Google OAuth: ✅/❌) |
| Stripe Payment | ✅ / ❌ | (live key: ✅/❌) |
| Creem Payment | ✅ / ❌ | (CREEM_DEBUG: true/false) |
| Beehiiv Newsletter | ✅ / ❌ | |
| Discord/Feishu Webhook | ✅ / ❌ | |
| Crisp Chat | ✅ / ❌ | |
| Analytics | ✅ / ❌ | (Plausible / GA / Umami / Clarity) |
环境变量状态
| 变量 | .env.local | .env.production | 说明 |
|---|---|---|---|
VITE_BASE_URL | ✅ | ✅ | 编译时 |
BETTER_AUTH_SECRET | ✅ | ❌ | 需要补(运行时 secret) |
STRIPE_SECRET_KEY | ✅ test | ❌ | 需要换 live key |
| ... |
待执行步骤(裁剪后共 N 步)
1. pnpm wrangler whoami(预期已登录账户 <account>) 2. (如缺)生成 BETTER_AUTH_SECRET → put 3. (如缺)补全 Stripe live key + price ID 4. pnpm wrangler secret bulk .env.production 5. pnpm db:migrate:remote 6. pnpm run deploy
确认放心跳过
- ✅ R2 桶已在远程存在(检测到 binding + 用户确认)
- ✅ D1 数据库已创建(database_id 已写入 wrangler.jsonc)
- ✅ Email Routing 上次部署已配置
风险提示
- ⚠️
<例:Stripe 当前是 test key,部署到生产会导致支付失败> - ⚠️
<例:域名 your-domain.com 未托管 Cloudflare,需要先去掉 routes 配置>
````
---
最终汇报模板
````markdown
🚀 部署完成
| 指标 | 结果 |
|---|---|
| Worker 包大小 | gzip <X> KiB / <Y> MB(< 3 MB 免费限制) |
| 启动耗时 | <Z> ms |
| 上传耗时 | <U>s |
| 触发耗时 | <V>s |
| 版本 ID | <id> |
| 自定义域名 | <domain> ✅ 已绑定 |
| 在线验证 | HTTP 200 / <bytes> KB / <time>s |
Checklist
- ✅ wrangler 登录(账户
<name>) - ✅ <按实际步骤勾选>
- ⚠️ <可选未启用项>
后续建议
1. 浏览器打开 https://<domain> 检查首页 / 关键路由 / 静态资源 2. Cloudflare Dashboard → Workers & Pages → <worker-name> → Logs 看 5min runtime 有无报错 3. (如启用支付)发起一笔测试交易确认 webhook 回调 4. (如启用 Auth)跑一遍登录 / 登出 / 注册流程 5. (如开 CI)推一个空 commit 验证 Actions 自动部署 ````
---
AskUserQuestion 模板(Phase 2)
供 SKILL.md Phase 2 直接引用。
域名状态(必问)
question: "<domain> 域名当前是否托管在 Cloudflare?"
options:
- "已托管 Cloudflare" — DNS 已在 Cloudflare 管理,可以直接用 custom_domain routes
- "未托管 / 不确定" — 先注释 routes,部署到 *.workers.dev,再到 Dashboard 绑定
- "还没买域名" — 直接用 Cloudflare 提供的 *.workers.devD1 / R2 资源(仅完整版)
question: "D1 数据库 <name> 在 Cloudflare 是否已创建?"
options:
- "已创建(database_id 已配)" — 跳过 wrangler d1 create
- "未创建" — 现在执行 wrangler d1 create
- "不知道" — 先 wrangler d1 list 查可选功能
question: "本次部署希望一次性启用哪些可选功能?" (multiSelect)
options:
- "Giscus 评论"
- "访问分析(Plausible/GA/Umami/Clarity)"
- "飞书/Discord Webhook"
- "Crisp Chat"
- "都先不启用,后续迭代再加"CI 自动化
question: "是否同时开通 GitHub Actions CI 自动部署?"
options:
- "开通自动部署" — push 到 main 自动触发
- "先手动 pnpm deploy" — CI 留到下一次迭代
- "保持 workflow_dispatch 手动触发" — 配 Secrets 但只从 GitHub 网页手动点模板:彻底关闭 D1 / R2 + 配套代码 stub
何时用:长期不启用 auth / payment / storage,又想避免创建 Cloudflare D1/R2(哪怕免费的)。
>
不推荐场景:未来 1-3 个月有可能启用 auth/payment 的项目 — 直接用空 D1/R2 资源(免费)+ websiteConfig.*.enable: false 更简单。>
背景:mkfast 的src/auth/auth.ts在 module top-level 调用getDb(),没有 D1 binding 直接 deploy 会让 worker 启动崩。本模板提供完整的"0 D1+R2 资源"代码改动清单。
---
4 个文件改动清单
① 删除 src/routes/api/auth/$.ts
整个文件删除(12 行),避免 import auth 触发 module-load-time 调用 getDb()。
rm src/routes/api/auth/$.ts影响:client 端 authClient.useSession() fetch /api/auth/get-session 拿到 404 → session=null → navbar.tsx 已有 websiteConfig.auth?.enable 判断 → 不渲染 user button → graceful。
② 改 src/db/index.ts — 加 lazy guard
import { drizzle } from 'drizzle-orm/d1';
import { schema } from './schema';
import { env } from 'cloudflare:workers';
export function getDb() {
if (!env.DB) {
throw new Error(
'D1 binding "DB" missing. Enable D1 in wrangler.jsonc or disable auth/payment.'
);
}
return drizzle(env.DB, { schema });
}影响:仅在被调用时 throw,不影响 import 链。任何还在 import getDb 的 server function(如 src/api/users.ts、src/payment/provider/stripe.ts)只要 client 不主动触发,就不会执行。
③ 改 src/auth/auth.ts — 条件导出 + stub
当前结构(line ~19-139):
export const auth = betterAuth({
baseURL: getBaseUrl(),
appName: websiteConfig.metadata?.name,
database: drizzleAdapter(getDb(), { provider: 'sqlite' }),
// ... 100+ 行配置
});改成:
import type { User } from 'better-auth';
import { betterAuth } from 'better-auth/minimal';
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
import { tanstackStartCookies } from 'better-auth/tanstack-start';
import { getDb } from '@/db';
import { sendEmail } from '@/mail';
import { subscribe } from '@/newsletter';
import { getBaseUrl } from '@/lib/urls';
import { serverEnv } from '@/env/server';
import { websiteConfig } from '@/config/website';
import { emailHarmony } from 'better-auth-harmony';
import { admin, apiKey } from 'better-auth/plugins';
const realAuth = websiteConfig.auth?.enable
? betterAuth({
// ↓ 原来 export const auth = betterAuth({ 后面的整段配置原样搬过来
baseURL: getBaseUrl(),
appName: websiteConfig.metadata?.name,
database: drizzleAdapter(getDb(), { provider: 'sqlite' }),
// ... 完整配置
})
: null;
const stubAuth = {
handler: async () =>
new Response('Auth disabled', { status: 404 }),
api: {
getSession: async () => null,
},
};
export const auth = (realAuth ?? stubAuth) as ReturnType<typeof betterAuth>;
// onCreateUser 函数原样保留(不被 module top 触发)
async function onCreateUser(user: User) {
// ... 原内容
}影响:
- enable=false 时
auth是 stub,auth.handler返回 404、auth.api.getSession()返回 null - middleware(
auth-middleware.ts/admin-middleware.ts)调用auth.api.getSession()拿 null → 走"未登录"分支 → 401,不会 module load 崩 - 未来要启用 auth:
websiteConfig.auth.enable: true即可,stub 自动让位给 real
④ 改 wrangler.jsonc — 删 D1 + R2 binding
// 删除整段:
"d1_databases": [
{
"binding": "DB",
"database_name": "...",
"database_id": "...",
"migrations_dir": "./src/db/migrations"
}
],
"r2_buckets": [
{
"bucket_name": "...",
"binding": "BUCKET"
}
]deploy 时不再校验 D1/R2 资源是否存在,worker bundle 体积也会略减。
---
不需要改的文件(已经是 lazy 的)
| 文件 | 为什么不用改 |
|---|---|
src/middlewares/auth-middleware.ts | 调 auth.api.getSession() 是 lazy;stubAuth 提供该方法返 null;middleware 走 if (!session?.user) return 401 正常 |
src/middlewares/admin-middleware.ts | 同上 |
src/api/users.ts / src/api/user-files.ts | 在 server function handler 内 lazy 调 getDb();用户访问对应 server function 才触发,触发时 throw → 该 server function 报 500,但首页/列表/详情/blog SSR 不受影响 |
src/payment/provider/stripe.ts | 在类方法内 lazy 调 getDb();payment.enable=false 时 client 不会触发 |
src/routes/api/storage/file.ts | server handler 内 lazy 调 getDb() + getStorageProvider();storage.enable=false 时 UI 不暴露入口 |
src/storage/index.ts / src/storage/provider/r2.ts | constructor 才检查 env.BUCKET,由 getStorageProvider() lazy 触发,module load OK |
---
Deploy 后预期
- ✅
pnpm wrangler deploy成功完成 - ✅ 网站首页
/返回 200 - ✅
/prompts、/blog、/prompts/[slug]等纯内容页正常响应 - ✅
/api/auth/*返回 404(route 已删,client 端 graceful handle) - ✅
/dashboard、/admin、/settings自动 redirect 到/(因为 auth.enable = false) - ✅ 没有 D1/R2 binding error,worker 顺利启动
未来要启用 auth / storage
按"创建空 D1+R2"路径恢复: 1. wrangler d1 create <project>-db + wrangler r2 bucket create <project>-files 2. 把 binding 重新加回 wrangler.jsonc 3. pnpm db:migrate:remote 4. wrangler secret put BETTER_AUTH_SECRET 5. websiteConfig.auth.enable: true 6. 不需要回退本模板的代码改动 — realAuth 在 enable=true 时自动取代 stub
唯一要回退的:src/routes/api/auth/$.ts 要重新创建(12 行原样):
import { createFileRoute } from '@tanstack/react-router';
import { auth } from '@/auth/auth';
export const Route = createFileRoute('/api/auth/$')({
server: {
handlers: {
GET: ({ request }) => auth.handler(request),
POST: ({ request }) => auth.handler(request),
},
},
});