< BACK Headless WordPress + Astro: A Working Setup for Content-Heavy Sites -- 线条艺术插图

Headless WordPress + Astro:内容密集型网站的实际可用方案

一个客户在 2023 年初给我打电话,他是一家媒体出版商,WordPress 上运行着大约 14,000 篇文章,主题是由四个开发人员在六年间拼凑起来的,Core Web Vitals 的评分真的令人尴尬。他们的 LCP 在手机上的时间是 7.2 秒。他们试过 WP Rocket,试过 CDN,甚至还删除了一半的插件。还是很慢。问题不在 WordPress 本身。问题在于每一次页面渲染都要经过 PHP、臃肿的主题和一个自 2018 年以来就没有审视过的数据库查询链。

关键要点:使用Astro的无头WordPress是内容网站的最佳方案:编辑用wp-admin,访客看到静态快速前端,WPGraphQL在两者之间架起桥梁。

那时我真正承诺采用以 Astro 为前端的无头设置。不是因为它很时髦,而是因为对于一个有数千篇文章和大量编辑内容的网站,这是唯一有意义的架构。

这就是我具体如何设置的。权衡、真实配置,以及那些给我教训的部分。

---

为什么选 Astro 而不是 Next.js

我经常被问这个问题。如果你的团队已经是React重度使用者,或者你需要复杂的客户端交互,Next.js是显而易见的答案。但对于内容密集型网站呢?Astro胜出,而且没有特别接近的竞争对手。

Astro 默认不附带任何 JavaScript。对于博客、新闻网站或文档门户,这是正确的默认选择。你在需要 JavaScript 的地方选择启用它,而不是选择禁用你大多数不想要的 200kb React 包。Astro 文档中关于部分水合的内容,他们称之为 Islands 架构,解释得比我用一句话能说的要更好,但简短版本是:只有交互的部分才需要 JS。文章正文、页眉、侧边栏?都是静态 HTML。

我在 2022 年末用 Next.js 和 WordPress 构建了一个法律内容网站。速度足够快,但客户总是问为什么他们的 Lighthouse 移动评分是 74,"现在应该是快的啊"。水合开销。用 Astro,同样类型的网站现在通常可以达到 95-98。不是吹牛,这就是这个架构免费给你的。

Astro不擅长的地方

得老实说。如果你的网站需要实时个性化、重型购物车,或任何真正依赖跨多个组件的客户端状态的东西,Astro就会开始显得很别扭。它不是React应用。Islands模式很强大,但它的思维模式与构建SPA不同。我在2023年中期试图在Astro项目中硬塞一个客户端仪表板,两周内就恢复到Next.js了。知道你在构建什么。

---

将WordPress设置为无头CMS

WordPress是一个真正好用的无头后端。WP REST API在核心中就有,文档完善,你的编辑团队不需要学习任何新东西。这最后一点比开发人员通常承认的要重要得多。

这是我使用的设置:

  1. 在一个子域名上安装 WordPress,我用 cms.yourdomain.com 或 api.yourdomain.com。至少用基本身份验证保护它,或者至少限制直接公网流量。前端是 yourdomain.com。两个独立的部署。
  2. 安装 [WPGraphQL 插件](https://www.wpgraphql.com/),对于内容网站,我更喜欢用 GraphQL 而不是 REST,因为你可以将查询与组件一起放置,并且只获取你需要的字段。不会过度获取。REST API 没问题,但一旦你有 15 个以上的自定义字段每个文章类型,GraphQL 的方法明显更干净。
  3. 安装 Advanced Custom Fields (ACF) 和 WPGraphQL for ACF 扩展。这个组合是让 WordPress 作为无头内容模型真正灵活的关键,你可以为每个文章类型定义结构化数据,通过 GraphQL 暴露它,Astro 可以干净地使用它。
  4. 禁用评论、表情符号和默认的 XML-RPC(如果你还没有的话)。这些会增加额外的开销和不必要的攻击面。
  5. 在开始构建之前,将固定链接设置为合理的格式。当你的 Astro 路由已经设置好后再更改它们真的很麻烦。

有一点会让人困惑:CORS。默认情况下,WordPress 不会让你的 Astro 开发服务器(运行在 localhost:4321 上)向你的 WP 安装发送请求。在开发期间,把这段代码放到你的主题 functions.php 或一个小实用插件中:

`` add_action('init', function() { header("Access-Control-Allow-Origin: *"); }); ``

在生产环境中将其限制为特定来源。显然。

---

Astro 项目结构

我在各个项目中保持这个结构的固定性和一致性。经过十多个无头构建项目后,这是行之有效的方法:

`` src/ components/ layouts/ pages/ index.astro blog/ [slug].astro lib/ wpgraphql.ts ← 所有 WP 查询逻辑都在这里 styles/ ``

lib/wpgraphql.ts 文件是我集中存放每一个 GraphQL 获取的地方。没有散落在页面文件中的内联 fetch 调用。每一个查询都是命名的、导出的异步函数。当凌晨 2 点有什么东西在 14,000 篇文章中出问题时调试这个,你以后会感谢自己。

在构建时获取文章

Astro 的 getStaticPaths 在这里是你的核心工具。对于有数千篇文章的博客:

`` export async function getStaticPaths() { const posts = await getAllPostSlugs(); // 调用 WPGraphQL return posts.map(post => ({ params: { slug: post.slug }, })); } ``

getAllPostSlugs 使用 after 游标对 WPGraphQL 进行分页,WordPress 的 GraphQL 层默认每个请求返回 100 篇文章,所以对于 14,000 篇文章,你在构建时要发送 140 个请求。听起来很吓人。实际上,在一个不错的服务器上,完整构建大约需要 4-5 分钟。对于每天重建几次的网站,这完全可以接受。

---

处理图片而不失去理智

这是很少有人充分讨论的问题。WordPress 将图片 URL 存储为指向你的 CMS 子域名。当 Astro 静态构建时,这些图片仍然存放在 cms.yourdomain.com 上,这意味着访问者的浏览器是从你的 WordPress 服务器获取图片,可能会绕过你的 CDN。

我处理这个的几种方式:

  • Cloudflare 放在两个域名前面。最简单的方案。通过 Cloudflare 代理 yourdomain.com 和 cms.yourdomain.com,在 /wp-content/uploads/* 上配置激进缓存,基本上就没问题了。
  • 使用媒体卸载插件。我喜欢 WP Offload Media,它会将上传的文件移至 S3(或兼容存储),并自动重写 URL。这是我对任何预期流量较大的网站采用的方法。你的 WordPress 服务器完全停止提供图片服务。
  • Astro 的 Image 组件。对于你在构建时控制的图片(通过 GraphQL 获取的特色图片),你可以将远程 URL 传递给 Astro 的 <Image> 组件,它会优化、调整大小,并从你的构建输出中提供服务。效果很好。但不适用于嵌入在文章内容 HTML 中的图片,那需要另外处理。

Seahawk 去年有一个旅游内容客户,大约 8,000 篇文章,图片非常密集,平均每篇 12 张图片。即使使用了无头设置,他们的 WordPress 服务器仍然因为图片请求而不堪重负。迁移到 S3 + CloudFront 后,他们的源服务器带宽下降了 94%。对他们的托管账单来说真是一个巨大的改变。

---

增量构建和重建问题

大规模静态生成的一个真实问题:你的编辑在下午 3 点发布了一个文章更正,却要等 5 分钟来完成一次完整重建。这在新闻编辑室是不可接受的。

我用过的几种方法:

选项 1:使用 Netlify 或 Vercel 的按需 ISR。Astro 支持带适配器的服务器端渲染,你可以在混合模式下运行 Astro,其中大多数页面是静态的,但特定路由按需渲染。对于新闻网站,我经常会静态预渲染最近 30 天的文章(流量高,需要速度),并将较早的存档页面设置为按需服务器渲染。两全其美。

选项 2:Webhook 触发的部分构建。WordPress 在文章保存时触发 webhook(使用 WP Webhooks 插件很容易)。该 webhook 触发 Netlify 或 Vercel 的部署钩子。构建运行时仅获取已更改的内容。这不是真正的部分构建,Astro 仍会重新构建所有内容,但如果你保持构建速度足够快,4 分钟是可以接受的。

选项 3:直接对整个应用使用 SSR。用 Node 适配器部署 Astro 到 VPS(我使用 Hetzner,便宜、快速、可靠)。每个页面都在请求时渲染,你在 Nginx 或 Cloudflare 级别进行激进缓存,实现即时文章更新。对于超过 50,000 篇文章的真正发布操作,这是我会做的选择。

说实话?大多数网站不需要选项 1 或 3 的复杂性。被 webhook 触发的 4 分钟重建对 90% 的内容网站来说已经足够了。

---

性能:你实际能获得的

关于开头提到的发布商项目,Astro 迁移后发生的情况是这样的:

  • 移动端 LCP 从 7.2 秒降到 1.1 秒(用 WebPageTest 从伦敦节点测试)
  • 总阻塞时间从约 800 毫秒降到 0 毫秒(记住,默认零 JS)
  • 他们的 Google Search Console Core Web Vitals 报告在部署后六周内从 3% "良好" URL 增至 91% "良好"
  • 托管成本下降了,因为他们的 WordPress 服务器不再供应页面,只提供 API 响应

这都不是魔法。当你把 PHP 渲染从关键路径中移除,不再向每个读者推送 400kb 的主题 JavaScript 包时,就会发生这样的事。

---

那些会让你踩坑的细节

说实话,这些是我在实际项目中必须调试的问题:

  • 草稿文章预览。在无头 WordPress 的设置中,这确实很烦人。WordPress 原生预览依赖前端渲染。你需要在 Astro 中构建一个自定义预览端点,接收 WordPress 预览 nonce,然后通过 WPGraphQL 获取草稿。不难,但要做到位需要花一天时间。
  • 重定向。如果旧网站在 .htaccess 中有几百个重定向,那么现在它们位于 WordPress 服务器上。你要么需要在 Astro 的配置中复制它们,要么让 WordPress 保持可访问并代理特定路径。我两种方法都试过。在 Astro 中复制从长期来看更整洁。
  • 搜索。在无头设置中,WordPress 内置搜索基本没用。我使用 Algolia 和 WP Search with Algolia 插件。在 WP 中索引文章,从 Astro Island 组件查询 Algolia。效果很好。
  • 菜单和导航。WordPress 菜单通过 WPGraphQL 暴露出来很难处理。wpgraphql-acf 路线往往更清晰,只需将你的导航建模为 ACF 转发器就行。

---

常见问题

我需要 WPGraphQL 还是可以直接用 REST API?

你完全可以使用 REST API,它内置在 WordPress 核心中,不需要任何额外的插件。对于拥有标准文章类型和最少自定义字段的简单网站来说,它很好用。GraphQL 的优势在于当你有复杂的内容模型,每种类型都有许多自定义字段时。能够在单个请求中获取你需要的确切字段,而不用费力处理 _embed 参数和嵌套的 REST 调用,这在你编写的每个查询上都能节省时间。选择权在你。我只是发现 GraphQL 在超过一定复杂度阈值时更简洁。

我如何为仅限成员的内容处理 WordPress 身份验证?

JWT 身份验证是标准方法。安装 JWT Authentication for WP REST API 插件,在登录时颁发令牌,在 GraphQL 请求标头中传递它们。在 Astro 这一侧,你需要用 SSR 路由(不是静态的)来处理这个问题,这样用户特定的内容会在每个请求时由服务器端获取。不要尝试静态构建这个,那样会很麻烦。

对于一个小博客来说,这是不是过度设计了?

是的,可能是。如果你有少于 500 篇文章和一个编辑,维护无头架构的开销就不值得了。只需使用一个好的 WordPress 主题,优化你的图像,然后继续你的工作。当你拥有大量内容、编辑复杂性或流量级别真正影响收入的时候,这种架构才能发挥作用。

生产环境中的托管设置是什么样的?

WordPress(仅作为 CMS)运行在小型 VPS 或托管 WordPress 主机上,我使用 Kinsta 或 Cloudways。Astro 前端部署在 Vercel、Netlify 或 Hetzner VPS(配合 Nginx)上,具体取决于项目。在所有东西前面放一个 Cloudflare。一个中等规模内容网站的月度总成本通常是 £60-£120,这通常比客户为一个一体化 WordPress 主机(在负载下艰难运行)支付的费用还要少。

---

诚实的总结是这样的:用 Astro 的无头 WordPress 是近年来内容站点最好的事情之一。不是因为它新,而是因为工具终于跟上了这个想法。WPGraphQL 已经稳定,Astro 的构建系统很快,性能提升是真实的和可测量的。

尽早理清架构,特别是你的图像策略和重建方法,这样你之后就能花更少时间去救火。就这么简单。

< BACK