# Deploy Static Site Cloudflare

> Deploy a local static HTML/CSS/JS marketing site to Cloudflare using Workers Static Assets (Git + wrangler), custom domain, and apex→www redirect. Use when the user asks to deploy a static website to Cloudflare, Cloudflare Workers, Cloudflare Pages, workers.dev, custom domain DNS from Namecheap, 404.html, wrangler.jsonc, .assetsignore, or kakar/ForgeLumen-style static hosting.

- Skill: `gaojuzhang/deploy-static-site-cloudflare` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add gaojuzhang/deploy-static-site-cloudflare`
- Raw SKILL.md: https://api.skillmd.com/api/skills/gaojuzhang/deploy-static-site-cloudflare/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Marketing & Growth
- Author: gaojuzhang (https://skillmd.com/u/gaojuzhang)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/gaojuzhang/deploy-static-site-cloudflare

---


# 静态站部署到 Cloudflare（Workers Static Assets）

## 何时使用

- 仓库是**纯静态站**（`index.html` + `assets/`，无框架构建，或仅有本地 `server.js` 预览）
- 用户要求部署到 Cloudflare、绑自定义域名、根域跳转到 www
- 遇到 Workers 构建失败（`Asset too large` / `workerd` / interactive wrangler setup）

## 官方推荐（默认采用）

**用 Workers Static Assets，不要新建经典 Pages 作为首选。**

依据：Cloudflare 文档写明新项目用 Workers Static Assets；Pages 仍可用，但新能力优先 Workers。静态资源请求免费；Deploy command 为 `npx wrangler deploy`。

经典 Pages（空 Build + Output `/`）仅在用户明确要求时使用。

## 执行原则

1. **先方案后改代码**；每步完成后汇报，等用户 Review 再进下一步
2. **最小改动**；Dashboard / 注册商操作需用户亲自完成（Agent 通常无 CF API Token / Namecheap）
3. 不确定点（DNS 放哪、生产分支名、是否根域跳转）**必须让用户决策**，勿默认拍板
4. 用中文与用户沟通

## 决策清单（开干前问清）

复制并勾选：

```
- [ ] 生产域名是什么？（例：www.example.com）
- [ ] 是否根域 example.com → www？（推荐：是）
- [ ] DNS 现状：已在 Cloudflare / 仍在 Namecheap 等注册商？
- [ ] 生产 Git 分支：master 或 main？（以 Cloudflare Production branch 为准，两边必须一致）
- [ ] 是否需要自定义 404？（Workers 需 404.html + not_found_handling）
```

**DNS 策略（二选一，推荐 A）：**

| 方案 | 做法 | 何时选 |
|------|------|--------|
| **A（推荐）** | 域名接入 Cloudflare Full DNS；注册商只改 Nameserver | 要绑 Custom Domain + 根域 Redirect Rule |
| B | DNS 留注册商；www CNAME → `*.workers.dev`；根域用注册商 URL Redirect | 暂不能改 NS |

## 进度清单

```
Task Progress:
- [ ] 步骤 0：勘察仓库
- [ ] 步骤 1：仓库准备（404 / 分支 / wrangler 配置）
- [ ] 步骤 2：Cloudflare 创建 Worker + Git 首次部署
- [ ] 步骤 3：域名接入 + www 绑定 + 根域→www
- [ ] 步骤 4：验收与收尾
```

---

## 步骤 0：勘察仓库

确认：

- 有无 `package.json` build？纯静态则 **无构建产物目录**
- 本地预览是否 `server.js`（仅本地，勿当线上入口）
- 是否已有 `404.html` / `error.html`
- `git remote`、当前默认分支、`sitemap.xml` / `robots.txt` 中的正式域名

---

## 步骤 1：仓库准备

### 1.1 自定义 404

- 根目录提供 **`404.html`**（可从 `error.html` 复制）
- 本地 `server.js` 可继续读 `error.html`；线上靠 Workers `not_found_handling`

### 1.2 生产分支与 Cloudflare 对齐

- Cloudflare Production branch 是什么，Git 默认分支就必须是什么
- 若 CF 构建仍拉已删除的分支 → 重建该分支或改 CF Production branch
- **只保留一条生产分支**，避免 `master`/`main` 双构建混淆

### 1.3 提交 Workers 配置（治本，必做）

在仓库根目录新增/更新以下文件（模板见 [reference.md](reference.md)）：

| 文件 | 作用 |
|------|------|
| `wrangler.jsonc` | `assets.directory: "."`，`not_found_handling: "404-page"`，`name` 与 CF 项目名一致 |
| `.assetsignore` | 排除 `node_modules`、`.git`、`server.js`、锁文件、配置文件等 |
| `.gitignore` | 至少忽略 `node_modules`、`.wrangler`、`.DS_Store` |
| `package.json` | 增加 `"deploy": "wrangler deploy"`（可选 `"preview": "wrangler dev"`） |

**为何必须提交 `wrangler.jsonc`：**  
无配置时 CI 里 `npx wrangler deploy` 会走交互 setup，把整个仓库当 assets；`bun/npm install` 装上的 `node_modules/workerd`（~144MiB）会触发 **Asset too large（单文件 > 25 MiB）**。Pages 会自动排除 `node_modules`；Workers **不会**，必须靠 `.assetsignore`。

### 1.4 公开 URL 规范：无 `.html` 后缀（必做，部署前完成）

Workers Static Assets **默认**会把 `page.html` 转到无后缀路径（常见 307 → `/page`）。规范做法是**一开始就按无后缀建设**，避免 sitemap/canonical 与最终地址分裂、事后再改。

| 规则 | 做法 |
|------|------|
| 磁盘文件 | 仍可为 `page.html`（不必改文件名） |
| **对外规范 URL** | **不带** `.html`，如 `/blog/my-post`、`/privacy-policy` |
| `sitemap.xml` 的 `<loc>` | 只用无后缀 URL |
| `rel=canonical` / `og:url` / JSON-LD | 与 sitemap 一致（无后缀） |
| 站内 `<a href>` | 链到无后缀（本地预览服务器需能解析，见下） |
| `wrangler.jsonc` | **不要**设 `html_handling: "none"`（那会强制保留 `.html`，与本规范冲突） |
| GSC 站点地图提交地址 | 仍是 `https://www.example.com/sitemap.xml`（改的是文件**内容**，不是地图 URL） |
| `robots.txt` Disallow | 旧页同时写带/不带 `.html` 两种路径更稳 |

本地 `server.js`：无扩展名请求失败时回退读取 `path + '.html'`，与线上行为对齐。

推送到生产分支后进入步骤 2。

---

## 步骤 2：Cloudflare Worker + Git 部署

Agent 无 Token 时，指导用户在 Dashboard 操作：

1. [Workers & Pages](https://dash.cloudflare.com/?to=/:account/workers-and-pages) → Create → 连接 GitHub
2. 授权并选择正确仓库
3. 配置：
   - Production branch = 仓库生产分支
   - Build command：可空（依赖由平台检测）；**Deploy command = `npx wrangler deploy`**
   - 勿依赖「无 wrangler.jsonc 的交互式生成」
4. Save and Deploy

### 弹窗与失败处理

| 现象 | 处理 |
|------|------|
| 「Upgrade to Workers Paid」 | 选 **Maybe later**；静态站免费档通常够用 |
| `error occurred while fetching repository` | 检查 GitHub Cloudflare App 仓库权限；分支是否存在 |
| `Asset too large` / `workerd` | 确认步骤 1.3 已推送；Retry |
| 构建仍指向错误分支 | Settings → Builds → Production branch |
| 线上最终 URL 无后缀但 sitemap 仍带 `.html` | 违反 §1.4；部署前改 sitemap/canonical/内链，勿设 `html_handling: "none"` |

### 步骤 2 验收

- Active deployment 成功
- `https://<name>.<subdomain>.workers.dev/` 首页正常
- 不存在路径返回自定义 404
- 汇报 URL，等用户确认后再进步骤 3

---

## 步骤 3：自定义域名（方案 A）

### 3.1 接入 Cloudflare Zone

1. Dashboard → Onboard domain → 输入 apex（如 `example.com`）→ Free
2. 记下 Cloudflare Nameservers
3. 注册商（如 Namecheap）→ Custom DNS → 填入 CF NS
4. 回 CF 点 Check nameservers，等到 **Active**

保留 **MX / 邮件相关 TXT**（如 Namecheap eforward）；删除指向旧主机的 www/apex **A/CNAME**。

### 3.2 绑定 `www` 到 Worker

路径：Worker → **Domains** → Add Domain（不是 Zone 的随便加点什么）

1. 选已接入的 zone（如 `example.com`）
2. Subdomain 填 **`www`**（只填子域，不要填 `www.example.com`）
3. 若报错：`Hostname already has externally managed DNS records`  
   → Zone DNS 里**删除**现有 `www` 的 A/AAAA/CNAME，再绑  
4. 让 CF 自动创建 DNS + 证书

### 3.3 根域 → www（301）

**不要**给 apex 也绑同一个 Worker Custom Domain（除非另有需求）。

1. DNS：apex `@` 添加 **A → `192.0.2.0`，Proxied（橙云）**  
   （originless 占位；流量不会打到该 IP，供 Redirect 使用）
2. **Redirect Rules 在 Zone 下，不在 Worker Domains 页**  
   - 进入域名 `example.com`（网站视图）→ 左侧 **Rules** → Overview  
   - 直达：[Rules Overview](https://dash.cloudflare.com/?to=/:account/:zone/rules/overview)  
   - Templates → **Redirect from root to www**，或手工：

```
When: Request URL wildcard  https://example.com/*
Then:  https://www.example.com/${1}
Status: 301
Preserve query string: On
```

文档：https://developers.cloudflare.com/rules/url-forwarding/examples/redirect-root-to-www/

### 步骤 3 验收

- `https://www.example.com/` 正常
- `https://example.com/` → 301 → `https://www.example.com/`
- `https://example.com/blog/` → 301 → 对应 www 路径
- `/robots.txt`、`/sitemap.xml`、404 正常

---

## 步骤 4：收尾

- Production branch 仅保留实际使用的分支
- `workers.dev` 可保留作预览；正式流量走 www
- 日常发布：`git push` 生产分支即可触发构建
- 不要提交密钥；`.dev.vars` 已在 `.gitignore`

---

## 参考与排障

- 文件模板、`.assetsignore` 完整列表、kakar 实例：[reference.md](reference.md)
- 常见错误对照表：[troubleshooting.md](troubleshooting.md)
- 个人 Skill 如何用 GitHub 管理：[skill-management.md](skill-management.md)

