这是我无头 WordPress 迁移系列的第 2 部分。无头 WordPress 部署(Headless WordPress deployment)真正的问题才开始出现,因为 DNS、SSL、图片、表单和重定向会以不同方式全部失效。在第 1 部分中,我用 Claude Code、Stitch MCP 和 Next.js 在一天内重建了前端。在本文中,我会展示我遇到的部署问题、我是如何修复的,以及为什么 AI 仍然需要人的判断。
大多数人都不谈的无头 WordPress 部署问题
搭建前端是容易的部分。以我的经验,无头 WordPress 部署一旦把一个网站拆成两个系统就会变难:Vercel 负责前端,WordPress 负责后端。你不再只有一个简单的技术栈。你会同时面对 DNS、证书、媒体 URL、REST 端点和管理访问,它们彼此之间在不同方向上拉扯。
当 `optagonen.se` 迁移到 Vercel 后,所有原本会请求到 WordPress 的流量都开始落到新的前端上。一次性就把 GraphQL、上传、Contact Form 7 和后台访问都弄坏了。如果你计划自己的 无头 WordPress 部署,在切换 DNS 之前你就需要为这些断点做好设计。
我是在真实的生产迁移中验证的,而不是在沙盒里。结论很明确:代码完成得很快,但基础设施花了时间。
为无头 WordPress 部署拆分 DNS
最干净的修复方式是给 WordPress 单独一个子域:`wp.optagonen.se`。这样在不强迫我把所有内容都通过一个源来反向代理的情况下,把前端和后端分离开来。排查 SSL 和媒体分发问题时,这种架构也更容易理解。
| Domain | Points to | Purpose |
|---|---|---|
| --- | --- | --- |
| `optagonen.se` | Vercel | Next.js 前端 |
| `wp.optagonen.se` | Origin server | WordPress 后端 |
我在 Hestia 中把 `wp.optagonen.se` 添加为别名,把 DNS A 记录指向源服务器的 IP,并更新了前端环境变量:
纸面上这部分很简单。实际中,无头 WordPress 部署暴露出连锁反应。每一个假设旧域名仍然有效的系统,都需要一个新的目标。
无头 WordPress 部署与 SSL 证书失败
首先坏掉的是 SSL。旧的 Let's Encrypt 证书只覆盖 `optagonen.se` 和 `www.optagonen.se`,所以 `https://wp.optagonen.se` 会立刻失败。这是预期行为。Let's Encrypt 会按主机名验证域名所有权,如果证书不匹配,浏览器就会拒绝连接。
Hestia 的内置续期失败了,因为它仍然试图通过 HTTP-01 验证主域名,但主域名现在指向了 Vercel。Certbot 失败的原因不同:Hestia 拦截了 ACME challenge,并返回了它自己的指纹,而不是 Certbot 的。AI 无法从第一性原理推断出这种冲突。你需要知道面板会如何表现。
我通过临时把 Hestia 的 ACME challenge 配置从 nginx include 路径中移出来修复:只为 `wp.optagonen.se` 签发证书,然后把续期后的文件再复制回 Hestia 的 SSL 目录。我也添加了续期钩子,让流程保持自动化。
为了建立信任,我参考了 Let's Encrypt 和 Certbot 的官方文档。这帮助我在再次动生产环境之前确认了 ACME 流程。
为什么无头 WordPress 部署拒绝了子域
SSL 一旦工作起来,WordPress 仍然不接受新的主机。它没有返回 GraphQL 数据,而是重定向到了 `/wp-signup.php`。这让我意识到 WordPress 不喜欢传入的 host header。
修复只需要一条 nginx 指令:
这一行让 WordPress 表现得就像请求仍然来自主域名,而浏览器仍停留在 `wp.optagonen.se`。在 无头 WordPress 部署中,这种 host 规范化比人们想象得更重要。WordPress 对站点 URL 非常有主见。
我在其他迁移里也见过同类问题:前端能工作、后端有响应,然后由于某个 host header 不匹配,session 流程会在不明显的情况下悄悄断掉。
无头 WordPress 部署后图片坏了
部署之后,每张图片都返回 502 错误。这是因为 WordPress 的媒体 URL 仍然指向 `/wp-content/uploads/...`,而这些路径现在会在 Vercel 上解析,而不是在源服务器上解析。在我的设置里,问题来自两个地方:静态文件里的硬编码 URL,以及 WPGraphQL 返回的动态 URL。
我先修复硬编码的 URL。我把所有 `https://optagonen.se/wp-content/uploads/` 引用替换为 `https://wp.optagonen.se/wp-content/uploads/`,覆盖了六个文件中的大约 70 个图片路径。
然后我用 Next.js rewrite 修复动态 GraphQL URL:
这样前端保持干净,也避免在 WordPress 内部重写媒体数据。它还让 无头 WordPress 部署更易维护:浏览器仍然请求相同的路径,而服务器负责代理转发。
如果你想更深入了解这种系统设计,我建议阅读我这篇 AI 驱动的 WordPress 迁移工作流→ 以及我的 多代理内容流水线→。这些文章展示了我如何围绕自动化来组织基础设施和内容系统。
联系表单、ID 和最后一次部署陷阱
Contact Form 7 起初看起来没问题,但表单停止工作是因为前端里的 ID 写错了。默认表单 ID 是 `1`,但 WordPress 里的真实表单是 `8`。通过一次快速的 API 检查确认了这一点。
这是个小修复,但很关键。在 无头 WordPress 部署中,一个错误的 ID 就可能让一个本来可用的表单看起来像是坏了,即使后端是健康的。我设置了 `CF7_FORM_ID=8`,表单立刻恢复上线。
这也是为什么我总是手动测试完整流程:
这个顺序比只靠日志猜测能更快捕捉问题。
AI 帮了什么,又漏了什么
Claude Code 帮我提速了,但它并没有替代理解。它很擅长处理重复性的部分:搜索配置文件、生成 nginx 片段、协助 certbot 命令、批量替换图片 URL。它还把错误链条保存在内存里,这在一次修复又引发另一个问题时节省了时间。
不过,AI 无法决定完整架构。它不知道 Hestia 会拦截 ACME 请求。它不知道什么时候应该用子域而不是反向代理。它也不知道为了避免停机,应该按什么顺序执行。
这就是 无头 WordPress 部署中 AI 的真正价值:它加速诊断,但你仍然需要理解技术栈的人。我的工作里也遵循同样的原则:我在构建 用于电商的 AI 自动化系统→ 时,以及在内容流水线工作中也是如此。
部署后的最终架构
修复完成后,技术栈稳定地分成了清晰的两部分:
| Component | Location | Purpose |
|---|---|---|
| --- | --- | --- |
| Next.js 前端 | Vercel | 提供页面并处理路由 |
| WordPress + WPGraphQL | `wp.optagonen.se` | 内容 API 与媒体存储 |
| Contact Form 7 | `wp.optagonen.se` | 表单处理 |
| 图片代理 | Next.js rewrite 规则 | 把上传路由到源服务器 |
| 前端 SSL | Vercel | 自动管理 |
| 后端 SSL | Certbot + 续期钩子 | 自动续期证书 |
这种结构稳定且易于调试。它也能在保留 WordPress 内部编辑工作流的同时,让前端保持快速。对于 无头 WordPress 部署来说,这种平衡就是目标。
从这次无头 WordPress 部署中学到的经验
下面是我从迁移中得到的结论:
这些经验来自真实的生产迁移,而不是教程。这也是为什么我现在会把部署所需的时间预算得和搭建一样多。
结论
无头 WordPress 部署并不难,难点不在代码。难在 DNS、SSL、媒体和 host headers 彼此都依赖对方。在这次迁移中,我修复了子域拆分、证书验证、图片分发以及 Contact Form 7,并且我清楚地知道 AI 在哪里能帮上忙、哪里就止步了。
如果你也在计划自己的迁移,先读前端重建的部分,尽早测试证书流程,并在上线前验证每一条媒体路径。然后检查线上站点,确认表单,并确保后端仍然以 WordPress 期望的方式工作。
如果你想要更多实用的部署拆解内容,继续阅读相关帖子,并把它们和你自己的技术栈对比。这是避免你下一次 无头 WordPress 部署犯错的最快方式。
建议的图片替代文本(alt text):
