各位好,把你们的键盘放下,把你们杯里的咖啡放下。我们今天不谈如何给 WordPress 装个插件让它跑得快两秒,也不谈那些把代码写得像面条一样的主题。我们今天要搞点刺激的——把 WordPress 这头臃肿的恐龙给开膛破肚,只留下最核心的骨架,然后用最现代的血液(FrankenPHP)给它重新接上,最后在它的脑袋上安一个超级智能的“前端”。
这就是所谓的“无损剥离”。
一、 为什么我们要“肢解” WordPress?
首先,让我们直面现实。WordPress 本身是一个伟大的发明,但它现在就像是一个在健身房练了十年的中年大叔。他不仅身体里塞满了脂肪(臃肿的代码),而且他的神经反射系统(单线程 PHP)慢得像是在泥地里骑自行车。
传统的 WordPress 架构是“堆栈式”的:
- HTTP 请求进来。
- Nginx/Apache 接管。
- 传递给 PHP-FPM。
- WordPress 核心加载(加载 SQL 查询、加载主题、加载插件、渲染 HTML)。
- 发送 HTML 给浏览器。
在这个过程中,WordPress 需要执行成千上万次数据库查询,每一行代码都在做着重复的工作:检查权限、过滤钩子、渲染 HTML 片段。这对于一个只需要数据 API 的应用来说,就像是让法拉利去运送一吨砖头,然后还要把砖头用胶水粘起来送过去。
Headless 的核心思想很简单:把“身子”和“头”分开。
- Body(WordPress): 只负责干活。写文章、存图片、查数据。它不关心页面长什么样,它只负责吐出 JSON 格式的数据。
- Head(前端): 负责展示。React, Vue, Next.js, Svelte……随便你挑。它只负责把数据画在屏幕上。
但你不能直接把 WordPress 拆了,它太大了。我们追求的是“无损剥离”。我们要保留 WordPress 的 CRUD 能力(增删改查),保留它的插件生态,但剥离掉它的 HTML 渲染逻辑。
二、 FrankenPHP:不是 PHP-FPM,而是 Caddy 的兄弟
很多人提到 PHP 性能优化,第一反应是换 Nginx。但今天我们要聊的是 Caddy 的亲兄弟——FrankenPHP。
为什么是 FrankenPHP?因为它不老,它不仅是个应用服务器,它还集成了 Caddy 的强大功能。这就好比你原来雇了一个厨师(PHP-FPM)和一个小弟(Nginx)来切菜,现在你直接雇了一个全能的保镖(FrankenPHP),他既能切菜,又能端盘子,还能帮你跟客户聊天(处理 HTTP 协议),最重要的是,他不需要你不停地给他发指令(处理 Keep-Alive 连接)。
FrankenPHP 的优势在于:
- 超时控制: PHP-FPM 最烦人的就是超时,稍微慢点就报 504 Gateway Timeout。FrankenPHP 基于事件循环,能更精细地控制资源。
- 内置 Caddy: 你不需要配置 Nginx,FrankenPHP 自带 Caddy 的配置能力,HTTP/3、TLS 自动续签、自动压缩,统统内置。
- PHP-FPM 池管理: 它比传统的 PHP-FPM 更聪明,能自动回收僵尸进程。
三、 动手:构建“无头”架构
我们要构建的架构是这样的:
- 前端: Next.js (负责渲染 UI,SEO 友好)。
- 后端: WordPress (负责数据)。
- 中间件: FrankenPHP (连接前后端)。
1. Docker Compose:搭建舞台
首先,我们需要一个 Docker 环境。别告诉我你还在本地 sudo apt install php-fpm,那是上个世纪的事情了。我们用 Docker Compose 来模拟一个完整的 Web 服务。
version: '3.8'
services:
# 数据库:MariaDB,比 MySQL 更轻量,但兼容性极佳
db:
image: mariadb:10.6
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: wordpress
MYSQL_USER: wp_user
MYSQL_PASSWORD: wp_pass
volumes:
- db_data:/var/lib/mysql
networks:
- wp_network
# 前端:Next.js,这是我们要给 WordPress 扛大旗的
frontend:
build: ./frontend
ports:
- "3000:3000"
networks:
- wp_network
depends_on:
- api
environment:
- NEXT_PUBLIC_API_URL=http://localhost:8080/graphql
# 后端核心:WordPress + FrankenPHP
wordpress:
image: dollybilly/frankenphp:latest
ports:
- "8080:8080" # HTTP/3
- "8443:8443" # HTTPS
volumes:
- ./wp-content:/var/www/html/wp-content
- ./frankenphp.toml:/etc/frankenphp/frankenphp.toml:ro
networks:
- wp_network
depends_on:
- db
networks:
wp_network:
driver: bridge
volumes:
db_data:
看这个配置,简单吗?简单。但是这里面藏着深意。
我们用 dollybilly/frankenphp 镜像,这可是目前最主流的 FrankenPHP 镜像之一。我们挂载了 frankenphp.toml,这个文件决定了 FrankenPHP 的行为。
2. FrankenPHP 配置:赋予它灵魂
[http_server]
https_port = 8443
[http_server.tls]
cert = "/etc/caddy/cert.pem"
key = "/etc/caddy/key.pem"
[php]
# 开启 PHP 的 OPcache,这就像给你的大脑装了个缓存芯片
opcache.enable = 1
opcache.memory_consumption = 128
opcache.max_accelerated_files = 4000
# PHP-FPM 池配置
[php.pool]
user = "www-data"
pm = "dynamic"
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
# 钩子:当 PHP 脚本结束时,自动清理内存
php_admin_value["memory_limit"] = "256M"
这段配置至关重要。pm = "dynamic" 让 FrankenPHP 根据负载动态调整 PHP 进程数量,避免了资源浪费。而 opcache 的开启,意味着编译过的 PHP 字节码会被直接缓存,而不需要每次都重新解析。
3. WordPress 核心逻辑剥离:WPGraphQL
接下来,我们需要在 WordPress 里安装 WPGraphQL 插件。这不仅仅是插件,这是通往未来的 API 钥匙。
传统 WordPress REST API 是扁平的,查询数据需要拼凑。而 GraphQL 像是一个多面手,前端想要什么数据,WordPress 就吐什么数据,不多不少,刚好一口。
// 在你的 WordPress 主题或插件中,启用 GraphQL 端点
add_action('init', function() {
// 注册 GraphQL Schema
WPGraphQL::register_schema();
});
// 这是一个简单的自定义类型示例
add_action('graphql_register_types', function() {
register_graphql_type('PostCustom', [
'description' => __('A custom post type', 'textdomain'),
'fields' => [
'custom_field' => [
'type' => 'String',
'resolve' => function($post) {
return get_post_meta($post->databaseId, 'my_secret_key', true);
}
]
]
]);
});
四、 前端渲染:Next.js 如何“接管”内容
现在,WordPress 后端(在 FrankenPHP 的守护下)正在 8080 端口上安静地运行,随时准备发送 JSON。前端 Next.js 站在 3000 端口,像个傲慢的指挥官。
我们使用 Next.js 的 getServerSideProps 来获取数据。
// pages/index.js
import { gql, useQuery } from '@apollo/client';
// 定义 GraphQL 查询语句
const GET_POSTS = gql`
query GetPosts {
posts(first: 10) {
edges {
node {
id
title
slug
content(format: RENDERED)
featuredImage {
node {
sourceUrl
}
}
}
}
}
}
`;
export default function Home() {
const { loading, error, data } = useQuery(GET_POSTS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<main>
<h1>Headless WordPress Power</h1>
{data.posts.edges.map(({ node }) => (
<article key={node.id}>
<h2>{node.title}</h2>
{node.featuredImage && (
<img src={node.featuredImage.node.sourceUrl} alt={node.title} />
)}
<div dangerouslySetInnerHTML={{ __html: node.content }} />
</article>
))}
</main>
);
}
看到了吗?这就是“剥离”的艺术。dangerouslySetInnerHTML 是唯一的“重操作”,因为我们需要把 PHP 生成的 HTML 字符串交给 React 去渲染。除此之外,Next.js 只是在做纯粹的 UI 渲染工作。
五、 加速路径:如何让速度起飞?
既然我们花了这么大功夫做 Headless,如果速度还不如以前的 WordPress,那我们就是找虐。这里有几条关键的加速路径。
1. 边缘缓存:不要让请求走回头路
FrankenPHP 最大的优势之一是它支持 Caddy 的强大的缓存机制。我们可以配置 FrankenPHP 在反向代理层进行缓存。
在 frankenphp.toml 中加入:
[http_server.routes]
route "/graphql" {
# 这里可以配置缓存策略,比如缓存 GET 请求
handle {
header Content-Type "application/json"
file_server browse
}
}
但这还不够。真正的加速来自于 Edge Side Rendering (SSR) with ISR (Incremental Static Regeneration)。
Next.js 允许我们将页面标记为 getStaticProps 并设置 revalidate。这意味着页面会在构建时生成,然后每隔一段时间(比如每小时)重新生成一次。
export async function getStaticProps(context) {
const res = await fetch('http://wordpress:8080/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: GET_POSTS,
variables: context.params
})
});
const data = await res.json();
return {
props: { posts: data },
revalidate: 3600 // 每小时重新验证一次,保证数据新鲜度,同时保持极速
};
}
这样,你的网站就像是一个静态站点,但内容又是动态更新的。用户访问时,毫秒级响应,不需要查询数据库。
2. 数据库连接池:不要让数据库窒息
WordPress 的数据库连接在传统的单线程模式下是阻塞的。每次查询就像是一次打电话给奶奶,要等她接电话、说完话、挂电话,整个流程才能继续。
在 FrankenPHP 和 PHP 8.2+ 的加持下,我们可以使用 PHP 的 Swoole 或 RoadRunner 扩展,或者利用 FrankenPHP 自身的进程池。这允许我们保持多个数据库连接处于“打开”状态,就像手里握着多条线在跟奶奶聊天,谁先说完就接谁,不用挂断。
这对于高并发场景至关重要。
3. 图片优化:前端来处理
传统 WordPress 会把图片塞得满满的。在 Headless 模式下,我们完全可以在前端使用 Next.js 的 <Image /> 组件。
import Image from 'next/image';
// 前端组件
export default function MyImage({ src, alt }) {
return (
<Image
src={src}
alt={alt}
width={500}
height={300}
// 这里 Next.js 会自动进行 WebP 转换、懒加载、响应式处理
/>
);
}
WordPress 只需要提供原图或者略小的 WebP 文件,剩下的脏活累活(压缩、懒加载、格式转换)全部交给 Next.js。这不仅加快了页面加载速度,还省去了 WordPress 后端的带宽压力。
六、 调试与维护:没有控制台的孤独
当你把 WordPress 变成 Headless 后,你失去了一些便利,但也获得了一些自由。
1. 没有 WP-Admin 怎么办?
这是最大的痛点。你不能直接登录后台写文章了。你需要使用 WPGraphQL Content Types (GQLCT) 或者 WPGraphQL for Headless CMS 这类插件,它们可以让你通过 GraphQL 编辑器直接在网页上编辑内容。
或者,你可以用 WP-CLI。FrankenPHP 很好地支持 WP-CLI。
# 在容器内执行
docker-compose exec wordpress wp user create
editor "[email protected]"
--role=editor
--user_pass=secretpassword
--allow-root
2. 404 错误的噩梦
在传统的 WordPress 中,404 错误很容易处理。在 Headless 模式下,如果前端路由找不到对应的 GraphQL 数据,你需要处理 notFound() 函数。
// pages/[slug].js
export async function getStaticPaths() {
// 获取所有文章的 slug
const res = await fetch('http://wordpress:8080/graphql', { ... });
const data = await res.json();
const paths = data.posts.edges.map(({ node }) => ({
params: { slug: node.slug },
}));
return { paths, fallback: 'blocking' };
}
export async function getStaticProps({ params }) {
const res = await fetch(`http://wordpress:8080/graphql?slug=${params.slug}`);
const data = await res.json();
if (!data.post) {
return { notFound: true };
}
return { props: { post: data.post } };
}
这里 fallback: 'blocking' 是关键。它告诉 Next.js:“如果这个页面不在缓存里,就等着我去查一下 GraphQL,查到了就返回,查不到就返回 404。”
七、 总结:未来的路
通过 FrankenPHP 和 Headless 架构,我们完成了一次对 WordPress 的“洗礼”。
- 剥离了 HTML 渲染: WordPress 不再是那个臃肿的生成器,它变成了一个纯净的数据 API。
- 剥离了架构耦合: 你可以随意更换前端技术栈,明天把 React 换成 Vue,或者把 Next.js 换成 Remix,WordPress 核心逻辑不需要改动一行代码。
- 剥离了性能瓶颈: FrankenPHP 的高效进程池和 Caddy 的事件驱动模型,解决了 PHP 单线程的顽疾。
这就像是把一辆老式拖拉机改装成了赛博朋克风格的无人驾驶飞行器。虽然拖拉机引擎(WordPress 核心)还在,虽然方向盘(数据库)还在,但现在的它,已经能在云端以超音速飞驰了。
别再犹豫了,下次当你看到 Fatal error: Allowed memory size of... 的时候,别再去改 wp-config.php 了。升级你的架构,拥抱 FrankenPHP,把头砍了,让身子飞起来。
Happy Coding, and may your render times be infinite.