01. 先看清每一层负责什么
这次最值得保留下来的不是某条命令,而是职责分层。当一个网站出了问题,如果不知道是哪一层负责什么,就会在 DNS、GitHub、Cloudflare、本机代码之间来回猜。
| 层 | 我们使用的东西 | 核心职责 | 典型故障 |
|---|---|---|---|
| 创作 / 执行 | ChatGPT + DevSpace MCP | 生成内容、修改文件、运行检查 | 代码写错、路径错误、工具链错误 |
| 本地源码 | F:\Personal-Tech-Site | 当前可编辑工作副本 | 文件缺失、语法错误、资源断链 |
| 版本控制 | Git + GitHub | 版本历史、远端备份、触发自动化 | 未提交、未 push、分支不一致 |
| 持续验证 | GitHub Actions | 对每次改动给出自动质量证据 | 测试失败、Runner 环境差异 |
| 托管 / CDN | Cloudflare Pages | 构建或发布静态文件并从边缘网络提供访问 | 部署失败、项目关联错误 |
| 访问入口 | DNS + tech.bestlonger.top | 把人类可读域名映射到站点服务 | NXDOMAIN、记录冲突、证书未就绪 |
“Pages 地址能打开,但自定义域名打不开”,优先看 DNS / Custom Domain;“本机正常但 GitHub CI 失败”,优先看 Runner 环境和测试脚本;“CI 和 Pages 都成功但页面内容不对”,再回到源码版本与缓存。
02. 为什么第一版坚持静态站
第一版只有 index.html、styles.css 和 app.js。这不是“技术简单”,而是有意识地减少运行时依赖。当前需求本质上是内容展示 + 轻量交互,没有登录、交易、实时数据库或服务端计算。
静态站的一个关键优势是:生产环境几乎没有“应用服务器状态”。浏览器请求 HTML/CSS/JS,Cloudflare Edge 直接返回文件。没有数据库连接池、没有长期运行的 Node/Python 服务,也没有服务器进程需要自己维护。
| 问题 | 静态站 | 全栈应用 |
|---|---|---|
| 部署复杂度 | 低,文件即可发布 | 需要构建、运行时、环境变量甚至数据库 |
| 攻击面 | 较小,无自建后端接口 | 需要保护 API、认证、依赖与服务器 |
| 扩展内容 | 适合文章、项目页、文档 | 适合动态用户数据和复杂业务 |
| 我们现在是否需要 | 是 | 暂时没有必要 |
真正需要 React/Vite、数据库或后端的时候再升级。工程设计里一个很重要的原则是:不要为了“看起来先进”提前购买复杂度。
03. GitHub 为什么成为整个链路的中枢
本机目录是编辑现场,但 GitHub 才是自动化系统共同能看到的“稳定交接点”。本地执行:
git add . git commit -m "..." git push
其中最关键的不是 push 把文件“上传”了,而是它产生了一个确定的 commit SHA。CI 和 Cloudflare Pages 都可以围绕同一个 SHA 工作,因此我们能回答:
- 哪个版本通过了 CI?
- 哪个版本被 Cloudflare 部署?
- 线上出问题时要回滚到哪个已知良好版本?
这就是 Git 从“备份工具”升级成“交付协议”的地方。
04. Cloudflare Pages 如何接住一次 Git push
GitHub 收到新 commit
仓库更新后,Cloudflare 的 Git integration 能看到对应分支的新版本。
Pages 创建 deployment
我们的站点没有构建步骤,所以部署的核心动作就是取得仓库内容并发布静态资源。
先落到 pages.dev
personal-tech-site.pages.dev 是 Cloudflare Pages 原生提供的项目地址。它是排障时非常重要的“基线地址”。
Custom Domain 映射正式入口
tech.bestlonger.top 成为面向读者的稳定地址,而底层仍由 Pages 托管。
这里有一个很实用的排障策略:先测试 pages.dev,再测试自定义域名。 如果前者 200、后者失败,问题通常就不在 HTML/CSS/JS,而在域名绑定、DNS 或证书链路。
05. 域名、DNS 与 HTTPS 到底做了什么
我们一开始讨论“要不要买域名”时,最容易混淆的概念是:域名不是网站本身,也不是托管空间。 它只是一个稳定、可读的名字。
这次的职责关系可以抽象成:
tech.bestlonger.top
│
│ DNS / Custom Domain
▼
Cloudflare Pages
│
▼
static assets
实际配置时,我们没有先随便手工造一条 CNAME,而是先在 Pages 项目的 Custom Domains 中声明 tech.bestlonger.top 属于这个项目。这样 Cloudflare 同时知道“DNS 指向哪里”和“这个 Hostname 应由哪个 Pages 项目回答”。
随后 HTTPS 证书由 Cloudflare 侧签发和托管。浏览器最终访问的是 https://tech.bestlonger.top,TLS 握手、边缘节点和内容分发都在 Cloudflare 的网络中完成。
06. CI/CD 的真实边界:现在还不是“CI 通过才发布”
这是今天最值得讲清楚的一点。很多文章会把 CI/CD 写成一根直线:
commit → CI → deploy
但我们当前真实架构并不是这样。GitHub 收到 push 后,GitHub Actions CI 和 Cloudflare Pages Deployment 是两个独立消费者:
┌─ GitHub Actions CI
git push ────┤
└─ Cloudflare Pages Deploy
因此 CI 失败并不自动阻止 Pages 发布。它现在的价值是:快速告诉我们“这个 commit 的质量检查失败了”。这已经很有用,但它仍然不是 blocking gate。
| 模式 | 触发方式 | CI 失败时 | 适合阶段 |
|---|---|---|---|
| 当前模式 | CI 与 Pages 并行 | 报警,但 Pages 可能已经部署 | 当前静态个人站,简单可靠 |
| 严格门控 | CI PASS 后由 Actions 部署 | 生产部署不会发生 | 有构建、测试、多人协作或高风险变更时 |
所以我们没有急着把系统复杂化。等网站发展到 Markdown 构建、搜索索引、更多脚本或多人协作时,再把 Cloudflare 部署迁入 GitHub Actions,形成真正的质量闸门。
07. 一次真实 CI 故障:不是网站坏了,而是测试脚本太乐观
把 Work Log #001 做成网页后,我们的 CI 真正失败过一次。Cloudflare Pages 已经部署成功,但 GitHub Actions 的 HTTP smoke test 报:
curl: (7) Failed to connect to 127.0.0.1 port 4173
最初脚本启动 Python HTTP Server 后只固定等待 1 秒:
python -m http.server 4173 --bind 127.0.0.1 & sleep 1 curl http://127.0.0.1:4173/
问题在于 CI Runner 的调度和进程启动速度不是确定值。sleep 1 只是“希望一秒够用”,并没有证明服务已经 ready。这是一类典型的 race condition / readiness problem。
我们把它改成有上限的重试:
for attempt in {1..10}; do
if curl --fail --silent http://127.0.0.1:4173/; then
break
fi
sleep 1
done
测试外部进程时,不要把“等待固定时间”当成“服务已经就绪”。优先检测真实 readiness 条件,并设置明确的最大等待时间和失败日志。
修复后,同一套 CI 同时验证首页和 Work Log #001 页面,最终 GitHub Actions 与 Cloudflare Pages 两项都恢复为 green。
08. 这次可以复用到其他项目的工程经验
- 先建立最小闭环,再增加复杂度。 本机能打开 → Git 可追踪 → GitHub 可恢复 → Pages 可部署 → 域名可访问 → CI 可验证。
- 保留一个基础访问入口。
pages.dev是判断“应用问题还是域名问题”的重要对照组。 - 自动化必须检查真实结果。 只做语法检查不够,所以 CI 最后真的启动 HTTP Server 并用 curl 访问页面。
- 把失败也写进日志。 “CI 曾经失败一次”比“全部顺利完成”更有学习价值,因为它揭示了 readiness 与并发触发的真实边界。
- 架构图必须表达真实系统,而不是理想系统。 所以 Archify 图里 CI 与 Pages 是分叉,而不是假装 CI 是部署前置条件。
下一阶段,网站会继续增加独立专题页、Markdown 文章构建、搜索与标签。但基础原则不变:每新增一层能力,都要知道它解决什么问题,也要知道它新增什么维护成本。